@savvy-web/silk 2.1.1 → 2.1.3

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.
@@ -1,5 +1,5 @@
1
1
  import { builtinModules, createRequire } from "node:module";
2
- import { Cache, Chunk, Context, Data, Duration, Effect, Exit, Layer, Option, Schema, Stream } from "effect";
2
+ import { Cache, Chunk, Context, Data, Duration, Effect, Exit, Function, Layer, Option, Schema, Stream } from "effect";
3
3
  import { Command, CommandExecutor, FileSystem, Path } from "@effect/platform";
4
4
  import * as path$1 from "node:path";
5
5
  import { basename, delimiter, dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
@@ -1289,7 +1289,7 @@ var YamlToken = class extends Schema.Class("YamlToken")({
1289
1289
  *
1290
1290
  * @public
1291
1291
  */
1292
- function createScanner$1(text) {
1292
+ function createScanner$2(text) {
1293
1293
  let pos = 0;
1294
1294
  let line = 0;
1295
1295
  let col = 0;
@@ -2240,7 +2240,7 @@ function createScanner$1(text) {
2240
2240
  * @public
2241
2241
  */
2242
2242
  function lex(text) {
2243
- return Stream.unfold(createScanner$1(text), (scanner) => {
2243
+ return Stream.unfold(createScanner$2(text), (scanner) => {
2244
2244
  const kind = scanner.scan();
2245
2245
  if (kind === null) return Option.none();
2246
2246
  const token = new YamlToken({
@@ -6386,7 +6386,7 @@ function parseDocument(text, options) {
6386
6386
  *
6387
6387
  * @public
6388
6388
  */
6389
- function parse$2(text, options) {
6389
+ function parse$3(text, options) {
6390
6390
  const uniqueKeys = options?.uniqueKeys ?? true;
6391
6391
  return parseDocument(text, options).pipe(Effect.flatMap((doc) => {
6392
6392
  if (uniqueKeys) {
@@ -6409,7 +6409,7 @@ function parse$2(text, options) {
6409
6409
  *
6410
6410
  * @public
6411
6411
  */
6412
- const workspaceManifestFromYaml = (content) => parse$2(content).pipe(Effect.mapError((e) => new CatalogAssemblyError({
6412
+ const workspaceManifestFromYaml = (content) => parse$3(content).pipe(Effect.mapError((e) => new CatalogAssemblyError({
6413
6413
  source: "manifest",
6414
6414
  reason: `invalid yaml: ${String(e)}`
6415
6415
  })), Effect.map((parsed) => {
@@ -6742,7 +6742,7 @@ var CatalogSet = class CatalogSet extends Schema.Class("CatalogSet")({ entries:
6742
6742
  *
6743
6743
  * @internal
6744
6744
  */
6745
- const lockfileCatalogsFromText = (text) => parse$2(text).pipe(Effect.map((parsed) => CatalogSet.fromLockfileCatalogs(parsed?.catalogs)), Effect.orElseSucceed(() => CatalogSet.empty()));
6745
+ const lockfileCatalogsFromText = (text) => parse$3(text).pipe(Effect.map((parsed) => CatalogSet.fromLockfileCatalogs(parsed?.catalogs)), Effect.orElseSucceed(() => CatalogSet.empty()));
6746
6746
  /**
6747
6747
  * Read the working tree's catalog state at `root`.
6748
6748
  *
@@ -8608,6 +8608,1301 @@ var WorkspaceInfo = class extends Schema.Class("WorkspaceInfo")({
8608
8608
  patterns: Schema.Array(Schema.String)
8609
8609
  }) {};
8610
8610
 
8611
+ //#endregion
8612
+ //#region ../../node_modules/.pnpm/jsonc-effect@0.3.1_effect@3.21.4/node_modules/jsonc-effect/errors.js
8613
+ /**
8614
+ * JSONC error types using Effect's Data.TaggedError pattern.
8615
+ *
8616
+ * @packageDocumentation
8617
+ */
8618
+ /**
8619
+ * Error codes representing specific JSONC parse failures.
8620
+ *
8621
+ * @remarks
8622
+ * Each code maps to a distinct syntactic error the parser can encounter,
8623
+ * from invalid symbols and number formats to missing delimiters and
8624
+ * unexpected end-of-input conditions.
8625
+ *
8626
+ * @see {@link JsoncParseErrorDetail} — carries one of these codes alongside
8627
+ * position information
8628
+ *
8629
+ * @public
8630
+ */
8631
+ const JsoncParseErrorCode = Schema.Literal("InvalidSymbol", "InvalidNumberFormat", "PropertyNameExpected", "ValueExpected", "ColonExpected", "CommaExpected", "CloseBraceExpected", "CloseBracketExpected", "EndOfFileExpected", "InvalidCommentToken", "UnexpectedEndOfComment", "UnexpectedEndOfString", "UnexpectedEndOfNumber", "InvalidUnicode", "InvalidEscapeCharacter", "InvalidCharacter");
8632
+ /**
8633
+ * Detail for a single parse error, including the error code, a human-readable
8634
+ * message, and the exact position within the source document.
8635
+ *
8636
+ * @remarks
8637
+ * - `code` — a {@link (JsoncParseErrorCode:type)} identifying the error kind.
8638
+ * - `message` — a descriptive message suitable for display.
8639
+ * - `offset` — zero-based character offset where the error occurred.
8640
+ * - `length` — character length of the problematic span.
8641
+ * - `startLine` — zero-based line number of the error.
8642
+ * - `startCharacter` — zero-based column within `startLine`.
8643
+ *
8644
+ * @see {@link JsoncParseError} — aggregates an array of these details
8645
+ *
8646
+ * @example
8647
+ * ```ts
8648
+ * import { JsoncParseErrorDetail } from "jsonc-effect";
8649
+ *
8650
+ * const detail = new JsoncParseErrorDetail({
8651
+ * code: "ValueExpected",
8652
+ * message: "Value expected",
8653
+ * offset: 5,
8654
+ * length: 1,
8655
+ * startLine: 0,
8656
+ * startCharacter: 5,
8657
+ * });
8658
+ *
8659
+ * console.log(detail.code); // "ValueExpected"
8660
+ * console.log(detail.offset); // 5
8661
+ * ```
8662
+ *
8663
+ * @public
8664
+ */
8665
+ var JsoncParseErrorDetail = class extends Schema.Class("JsoncParseErrorDetail")({
8666
+ code: JsoncParseErrorCode,
8667
+ message: Schema.String,
8668
+ offset: Schema.Number,
8669
+ length: Schema.Number,
8670
+ startLine: Schema.Number,
8671
+ startCharacter: Schema.Number
8672
+ }) {};
8673
+ /**
8674
+ * Base class for {@link JsoncParseError}; not intended to be constructed or
8675
+ * caught directly — use `JsoncParseError` instead.
8676
+ *
8677
+ * @privateRemarks
8678
+ * The `*Base` pattern is required because `Data.TaggedError` produces complex
8679
+ * type signatures involving intersection types and branded generics that
8680
+ * api-extractor cannot roll up into a single `.d.ts` bundle. Exporting the
8681
+ * base separately lets the public `JsoncParseError` class extend it with
8682
+ * concrete fields, giving api-extractor a simple class declaration to work
8683
+ * with. It is tagged `@public` (rather than `@internal`) because it appears
8684
+ * in `JsoncParseError`'s heritage clause in the public `.d.ts`, and API
8685
+ * Extractor requires release tags to be compatible across a signature.
8686
+ *
8687
+ * @public
8688
+ */
8689
+ const JsoncParseErrorBase = Data.TaggedError("JsoncParseError");
8690
+ /**
8691
+ * Error raised when JSONC parsing encounters one or more syntax errors.
8692
+ *
8693
+ * @remarks
8694
+ * Contains the full source `text`, the `options` used for parsing, and an
8695
+ * `errors` array of {@link JsoncParseErrorDetail} instances with precise
8696
+ * position information for each problem found.
8697
+ *
8698
+ * @see {@link parse} — may fail with this error
8699
+ * @see {@link parseTree} — may fail with this error
8700
+ *
8701
+ * @example Catching with `Effect.catchTag`
8702
+ * ```ts
8703
+ * import { Effect } from "effect";
8704
+ * import { parse } from "jsonc-effect";
8705
+ *
8706
+ * const program = parse("{ invalid }").pipe(
8707
+ * Effect.catchTag("JsoncParseError", (e) => {
8708
+ * console.error(e.errors); // Array of JsoncParseErrorDetail
8709
+ * return Effect.succeed({});
8710
+ * }),
8711
+ * );
8712
+ * ```
8713
+ *
8714
+ * @example Inspecting error details
8715
+ * ```ts
8716
+ * import { Effect } from "effect";
8717
+ * import { parse } from "jsonc-effect";
8718
+ *
8719
+ * const program = parse("{ invalid }").pipe(
8720
+ * Effect.catchTag("JsoncParseError", (e) => {
8721
+ * for (const detail of e.errors) {
8722
+ * console.error(
8723
+ * `[${detail.code}] ${detail.message} at line ${detail.startLine}:${detail.startCharacter}`,
8724
+ * );
8725
+ * }
8726
+ * return Effect.succeed({});
8727
+ * }),
8728
+ * );
8729
+ * ```
8730
+ *
8731
+ * @public
8732
+ */
8733
+ var JsoncParseError = class extends JsoncParseErrorBase {
8734
+ get message() {
8735
+ const count = this.errors.length;
8736
+ return `JSONC parse failed with ${count} error${count !== 1 ? "s" : ""}: ${this.errors.map((e) => e.message).join("; ")}`;
8737
+ }
8738
+ };
8739
+ /**
8740
+ * Base class for {@link JsoncNodeNotFoundError}; not intended to be
8741
+ * constructed or caught directly — use `JsoncNodeNotFoundError` instead.
8742
+ *
8743
+ * @privateRemarks
8744
+ * Uses the same `*Base` pattern as {@link JsoncParseErrorBase} to work
8745
+ * around api-extractor's inability to roll up the complex type produced
8746
+ * by `Data.TaggedError` into a single `.d.ts` declaration. Tagged `@public`
8747
+ * for the same heritage-clause-compatibility reason as `JsoncParseErrorBase`.
8748
+ *
8749
+ * @public
8750
+ */
8751
+ const JsoncNodeNotFoundErrorBase = Data.TaggedError("JsoncNodeNotFoundError");
8752
+ /**
8753
+ * Base class for {@link JsoncModificationError}; not intended to be
8754
+ * constructed or caught directly — use `JsoncModificationError` instead.
8755
+ *
8756
+ * @privateRemarks
8757
+ * Uses the same `*Base` pattern as {@link JsoncParseErrorBase} to work
8758
+ * around api-extractor's inability to roll up the complex type produced
8759
+ * by `Data.TaggedError` into a single `.d.ts` declaration. Tagged `@public`
8760
+ * for the same heritage-clause-compatibility reason as `JsoncParseErrorBase`.
8761
+ *
8762
+ * @public
8763
+ */
8764
+ const JsoncModificationErrorBase = Data.TaggedError("JsoncModificationError");
8765
+ /**
8766
+ * Error raised when {@link modify} produces invalid edits or encounters
8767
+ * an unsupported modification scenario.
8768
+ *
8769
+ * @remarks
8770
+ * Contains the `path` where modification was attempted and a `reason`
8771
+ * string explaining why it failed.
8772
+ *
8773
+ * @see {@link modify} — may fail with this error
8774
+ *
8775
+ * @example
8776
+ * ```ts
8777
+ * import { Effect } from "effect";
8778
+ * import { modify } from "jsonc-effect";
8779
+ *
8780
+ * const program = modify("{}", ["deep", "path"], 42).pipe(
8781
+ * Effect.catchTag("JsoncModificationError", (e) => {
8782
+ * console.error(`Failed at [${e.path.join(", ")}]: ${e.reason}`);
8783
+ * return Effect.succeed([]);
8784
+ * }),
8785
+ * );
8786
+ * ```
8787
+ *
8788
+ * @public
8789
+ */
8790
+ var JsoncModificationError = class extends JsoncModificationErrorBase {
8791
+ get message() {
8792
+ return `Modification failed at path [${this.path.join(", ")}]: ${this.reason}`;
8793
+ }
8794
+ };
8795
+
8796
+ //#endregion
8797
+ //#region ../../node_modules/.pnpm/jsonc-effect@0.3.1_effect@3.21.4/node_modules/jsonc-effect/scanner.js
8798
+ const isWhitespace = (ch) => ch === 32 || ch === 9 || ch === 11 || ch === 12 || ch === 160 || ch === 65279;
8799
+ const isLineBreak$1 = (ch) => ch === 10 || ch === 13 || ch === 8232 || ch === 8233;
8800
+ const isDigit$1 = (ch) => ch >= 48 && ch <= 57;
8801
+ /**
8802
+ * Create a stateful {@link JsoncScanner} for the given JSONC string.
8803
+ *
8804
+ * @param text - JSONC string to tokenize
8805
+ * @param ignoreTrivia - If `true`, the scanner automatically skips whitespace,
8806
+ * line-break, and comment tokens so that only structural tokens are returned
8807
+ * (default: `false`).
8808
+ * @returns A stateful {@link JsoncScanner} positioned before the first token.
8809
+ *
8810
+ * @remarks
8811
+ * When `ignoreTrivia` is `true` the scanner is suitable for building parsers
8812
+ * that only care about structural tokens (`OpenBrace`, `String`, `Number`,
8813
+ * etc.). Set it to `false` (the default) when you need to preserve comments
8814
+ * or whitespace — for example in a formatter or a comment-stripping pass.
8815
+ *
8816
+ * @see {@link JsoncScanner} — the interface returned by this factory
8817
+ * @see {@link parse} — higher-level API that uses a scanner internally
8818
+ *
8819
+ * @example
8820
+ * Tokenizing a JSONC string and printing each token:
8821
+ * ```ts
8822
+ * import type { JsoncSyntaxKind } from "jsonc-effect";
8823
+ * import { createScanner } from "jsonc-effect";
8824
+ *
8825
+ * const scanner = createScanner('{ "name": "jsonc" }', true);
8826
+ * let kind: JsoncSyntaxKind;
8827
+ * do {
8828
+ * kind = scanner.scan();
8829
+ * console.log(kind, scanner.getTokenValue());
8830
+ * } while (kind !== "EOF");
8831
+ * ```
8832
+ *
8833
+ * @privateRemarks
8834
+ * Ported from Microsoft's jsonc-parser (MIT), adapted to use string literal
8835
+ * token types instead of numeric enums.
8836
+ *
8837
+ * @public
8838
+ */
8839
+ const createScanner$1 = (text, ignoreTrivia = false) => {
8840
+ const len = text.length;
8841
+ let pos = 0;
8842
+ let tokenOffset = 0;
8843
+ let token = "Unknown";
8844
+ let tokenValue = "";
8845
+ let tokenError = "None";
8846
+ let lineNumber = 0;
8847
+ let lineStartOffset = 0;
8848
+ let tokenStartLine = 0;
8849
+ let tokenStartCharacter = 0;
8850
+ const scanHexDigits = (count) => {
8851
+ let value = 0;
8852
+ for (let i = 0; i < count; i++) {
8853
+ if (pos >= len) return -1;
8854
+ const ch = text.charCodeAt(pos);
8855
+ if (ch >= 48 && ch <= 57) value = value * 16 + (ch - 48);
8856
+ else if (ch >= 65 && ch <= 70) value = value * 16 + (ch - 65 + 10);
8857
+ else if (ch >= 97 && ch <= 102) value = value * 16 + (ch - 97 + 10);
8858
+ else return -1;
8859
+ pos++;
8860
+ }
8861
+ return value;
8862
+ };
8863
+ const scanString = () => {
8864
+ let result = "";
8865
+ pos++;
8866
+ let start = pos;
8867
+ while (pos < len) {
8868
+ const ch = text.charCodeAt(pos);
8869
+ if (ch === 34) {
8870
+ result += text.substring(start, pos);
8871
+ pos++;
8872
+ return result;
8873
+ }
8874
+ if (ch === 92) {
8875
+ result += text.substring(start, pos);
8876
+ pos++;
8877
+ if (pos >= len) {
8878
+ tokenError = "UnexpectedEndOfString";
8879
+ return result;
8880
+ }
8881
+ const escaped = text.charCodeAt(pos);
8882
+ pos++;
8883
+ switch (escaped) {
8884
+ case 34:
8885
+ result += "\"";
8886
+ break;
8887
+ case 92:
8888
+ result += "\\";
8889
+ break;
8890
+ case 47:
8891
+ result += "/";
8892
+ break;
8893
+ case 98:
8894
+ result += "\b";
8895
+ break;
8896
+ case 102:
8897
+ result += "\f";
8898
+ break;
8899
+ case 110:
8900
+ result += "\n";
8901
+ break;
8902
+ case 114:
8903
+ result += "\r";
8904
+ break;
8905
+ case 116:
8906
+ result += " ";
8907
+ break;
8908
+ case 117: {
8909
+ const value = scanHexDigits(4);
8910
+ if (value >= 0) result += String.fromCharCode(value);
8911
+ else tokenError = "InvalidUnicode";
8912
+ break;
8913
+ }
8914
+ default:
8915
+ tokenError = "InvalidEscapeCharacter";
8916
+ break;
8917
+ }
8918
+ start = pos;
8919
+ } else if (isLineBreak$1(ch)) {
8920
+ tokenError = "UnexpectedEndOfString";
8921
+ return result + text.substring(start, pos);
8922
+ } else pos++;
8923
+ }
8924
+ tokenError = "UnexpectedEndOfString";
8925
+ return result + text.substring(start, pos);
8926
+ };
8927
+ const scanNumber = () => {
8928
+ const start = pos;
8929
+ if (text.charCodeAt(pos) === 45) pos++;
8930
+ if (text.charCodeAt(pos) === 48) pos++;
8931
+ else {
8932
+ if (!isDigit$1(text.charCodeAt(pos))) {
8933
+ tokenError = "UnexpectedEndOfNumber";
8934
+ return text.substring(start, pos);
8935
+ }
8936
+ pos++;
8937
+ while (pos < len && isDigit$1(text.charCodeAt(pos))) pos++;
8938
+ }
8939
+ if (pos < len && text.charCodeAt(pos) === 46) {
8940
+ pos++;
8941
+ if (!isDigit$1(text.charCodeAt(pos))) {
8942
+ tokenError = "UnexpectedEndOfNumber";
8943
+ return text.substring(start, pos);
8944
+ }
8945
+ pos++;
8946
+ while (pos < len && isDigit$1(text.charCodeAt(pos))) pos++;
8947
+ }
8948
+ if (pos < len && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
8949
+ pos++;
8950
+ if (pos < len && (text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45)) pos++;
8951
+ if (!isDigit$1(text.charCodeAt(pos))) {
8952
+ tokenError = "UnexpectedEndOfNumber";
8953
+ return text.substring(start, pos);
8954
+ }
8955
+ pos++;
8956
+ while (pos < len && isDigit$1(text.charCodeAt(pos))) pos++;
8957
+ }
8958
+ return text.substring(start, pos);
8959
+ };
8960
+ const scan = () => {
8961
+ tokenValue = "";
8962
+ tokenError = "None";
8963
+ if (pos >= len) {
8964
+ tokenOffset = len;
8965
+ tokenStartLine = lineNumber;
8966
+ tokenStartCharacter = pos - lineStartOffset;
8967
+ token = "EOF";
8968
+ return token;
8969
+ }
8970
+ let ch = text.charCodeAt(pos);
8971
+ if (isWhitespace(ch)) {
8972
+ tokenOffset = pos;
8973
+ tokenStartLine = lineNumber;
8974
+ tokenStartCharacter = pos - lineStartOffset;
8975
+ do {
8976
+ pos++;
8977
+ ch = pos < len ? text.charCodeAt(pos) : 0;
8978
+ } while (isWhitespace(ch));
8979
+ tokenValue = text.substring(tokenOffset, pos);
8980
+ if (ignoreTrivia) return scan();
8981
+ token = "Trivia";
8982
+ return token;
8983
+ }
8984
+ if (isLineBreak$1(ch)) {
8985
+ tokenOffset = pos;
8986
+ tokenStartLine = lineNumber;
8987
+ tokenStartCharacter = pos - lineStartOffset;
8988
+ pos++;
8989
+ if (ch === 13 && pos < len && text.charCodeAt(pos) === 10) pos++;
8990
+ lineNumber++;
8991
+ lineStartOffset = pos;
8992
+ tokenValue = text.substring(tokenOffset, pos);
8993
+ if (ignoreTrivia) return scan();
8994
+ token = "LineBreak";
8995
+ return token;
8996
+ }
8997
+ tokenOffset = pos;
8998
+ tokenStartLine = lineNumber;
8999
+ tokenStartCharacter = pos - lineStartOffset;
9000
+ switch (ch) {
9001
+ case 123:
9002
+ pos++;
9003
+ tokenValue = "{";
9004
+ token = "OpenBrace";
9005
+ return token;
9006
+ case 125:
9007
+ pos++;
9008
+ tokenValue = "}";
9009
+ token = "CloseBrace";
9010
+ return token;
9011
+ case 91:
9012
+ pos++;
9013
+ tokenValue = "[";
9014
+ token = "OpenBracket";
9015
+ return token;
9016
+ case 93:
9017
+ pos++;
9018
+ tokenValue = "]";
9019
+ token = "CloseBracket";
9020
+ return token;
9021
+ case 58:
9022
+ pos++;
9023
+ tokenValue = ":";
9024
+ token = "Colon";
9025
+ return token;
9026
+ case 44:
9027
+ pos++;
9028
+ tokenValue = ",";
9029
+ token = "Comma";
9030
+ return token;
9031
+ case 34:
9032
+ tokenValue = scanString();
9033
+ token = "String";
9034
+ return token;
9035
+ case 47: {
9036
+ const nextCh = pos + 1 < len ? text.charCodeAt(pos + 1) : 0;
9037
+ if (nextCh === 47) {
9038
+ pos += 2;
9039
+ while (pos < len && !isLineBreak$1(text.charCodeAt(pos))) pos++;
9040
+ tokenValue = text.substring(tokenOffset, pos);
9041
+ if (ignoreTrivia) return scan();
9042
+ token = "LineComment";
9043
+ return token;
9044
+ }
9045
+ if (nextCh === 42) {
9046
+ pos += 2;
9047
+ const safeLen = len - 1;
9048
+ let commentClosed = false;
9049
+ while (pos < safeLen) {
9050
+ const cch = text.charCodeAt(pos);
9051
+ if (isLineBreak$1(cch)) {
9052
+ if (cch === 13 && pos + 1 < len && text.charCodeAt(pos + 1) === 10) pos++;
9053
+ pos++;
9054
+ lineNumber++;
9055
+ lineStartOffset = pos;
9056
+ } else if (cch === 42 && text.charCodeAt(pos + 1) === 47) {
9057
+ pos += 2;
9058
+ commentClosed = true;
9059
+ break;
9060
+ } else pos++;
9061
+ }
9062
+ if (!commentClosed) {
9063
+ pos = len;
9064
+ tokenError = "UnexpectedEndOfComment";
9065
+ }
9066
+ tokenValue = text.substring(tokenOffset, pos);
9067
+ if (ignoreTrivia) return scan();
9068
+ token = "BlockComment";
9069
+ return token;
9070
+ }
9071
+ pos++;
9072
+ tokenValue = text.substring(tokenOffset, pos);
9073
+ token = "Unknown";
9074
+ tokenError = "InvalidCharacter";
9075
+ return token;
9076
+ }
9077
+ case 45:
9078
+ if (pos + 1 < len && isDigit$1(text.charCodeAt(pos + 1))) {
9079
+ tokenValue = scanNumber();
9080
+ token = "Number";
9081
+ return token;
9082
+ }
9083
+ pos++;
9084
+ tokenValue = "-";
9085
+ token = "Unknown";
9086
+ tokenError = "InvalidSymbol";
9087
+ return token;
9088
+ default:
9089
+ if (isDigit$1(ch)) {
9090
+ tokenValue = scanNumber();
9091
+ token = "Number";
9092
+ return token;
9093
+ }
9094
+ if (ch >= 97 && ch <= 122) {
9095
+ const start = pos;
9096
+ pos++;
9097
+ while (pos < len) {
9098
+ const kch = text.charCodeAt(pos);
9099
+ if (kch >= 97 && kch <= 122) pos++;
9100
+ else break;
9101
+ }
9102
+ tokenValue = text.substring(start, pos);
9103
+ switch (tokenValue) {
9104
+ case "true":
9105
+ token = "True";
9106
+ return token;
9107
+ case "false":
9108
+ token = "False";
9109
+ return token;
9110
+ case "null":
9111
+ token = "Null";
9112
+ return token;
9113
+ default:
9114
+ token = "Unknown";
9115
+ tokenError = "InvalidSymbol";
9116
+ return token;
9117
+ }
9118
+ }
9119
+ pos++;
9120
+ tokenValue = text.substring(tokenOffset, pos);
9121
+ token = "Unknown";
9122
+ tokenError = "InvalidCharacter";
9123
+ return token;
9124
+ }
9125
+ };
9126
+ return {
9127
+ scan,
9128
+ getToken: () => token,
9129
+ getTokenValue: () => tokenValue,
9130
+ getTokenOffset: () => tokenOffset,
9131
+ getTokenLength: () => pos - tokenOffset,
9132
+ getTokenStartLine: () => tokenStartLine,
9133
+ getTokenStartCharacter: () => tokenStartCharacter,
9134
+ getTokenError: () => tokenError,
9135
+ getPosition: () => pos,
9136
+ setPosition: (newPos) => {
9137
+ pos = newPos;
9138
+ tokenValue = "";
9139
+ token = "Unknown";
9140
+ tokenError = "None";
9141
+ }
9142
+ };
9143
+ };
9144
+
9145
+ //#endregion
9146
+ //#region ../../node_modules/.pnpm/jsonc-effect@0.3.1_effect@3.21.4/node_modules/jsonc-effect/parse.js
9147
+ /**
9148
+ * JSONC Parser — converts token stream into JavaScript values or AST nodes.
9149
+ *
9150
+ * Pure Effect implementation using recursive descent parsing.
9151
+ * Reference: Microsoft's jsonc-parser parser design (MIT).
9152
+ *
9153
+ * @packageDocumentation
9154
+ */
9155
+ /**
9156
+ * Parse a JSONC string into a JavaScript value.
9157
+ *
9158
+ * @param text - JSONC string to parse
9159
+ * @param options - Optional {@link JsoncParseOptions} controlling comment and
9160
+ * trailing-comma handling.
9161
+ * @returns `Effect<unknown, JsoncParseError>` — succeeds with the parsed value
9162
+ * or fails with a {@link JsoncParseError} containing every error encountered.
9163
+ *
9164
+ * @remarks
9165
+ * The return type is `unknown` (not `any`) so consumers are forced to narrow
9166
+ * the result, which is safer in Effect pipelines. By default
9167
+ * `allowTrailingComma` is `true`, matching common JSONC conventions used in
9168
+ * VS Code settings and `tsconfig.json`.
9169
+ *
9170
+ * @see {@link parseTree} — parse into an AST instead of a plain value
9171
+ * @see {@link JsoncParseOptions} — available parse options
9172
+ * @see {@link JsoncParseError} — the tagged error type on the failure channel
9173
+ *
9174
+ * @example
9175
+ * Basic parsing:
9176
+ * ```ts
9177
+ * import { Effect } from "effect";
9178
+ * import { parse } from "jsonc-effect";
9179
+ *
9180
+ * const value = Effect.runSync(parse('{ "key": 42 }'));
9181
+ * console.log(value); // { key: 42 }
9182
+ * ```
9183
+ *
9184
+ * @example
9185
+ * Parsing with options:
9186
+ * ```ts
9187
+ * import { Effect } from "effect";
9188
+ * import { parse } from "jsonc-effect";
9189
+ *
9190
+ * const value = Effect.runSync(
9191
+ * parse('{ "key": 42 }', { disallowComments: true }),
9192
+ * );
9193
+ * ```
9194
+ *
9195
+ * @example
9196
+ * Error handling with `catchTag`:
9197
+ * ```ts
9198
+ * import { Effect } from "effect";
9199
+ * import { parse } from "jsonc-effect";
9200
+ *
9201
+ * const program = parse("{ bad }").pipe(
9202
+ * Effect.catchTag("JsoncParseError", (err) =>
9203
+ * Effect.succeed({ fallback: true, errors: err.errors }),
9204
+ * ),
9205
+ * );
9206
+ *
9207
+ * const result = Effect.runSync(program);
9208
+ * console.log(result);
9209
+ * ```
9210
+ *
9211
+ * @example
9212
+ * Using `Effect.gen`:
9213
+ * ```ts
9214
+ * import { Effect } from "effect";
9215
+ * import { parse } from "jsonc-effect";
9216
+ *
9217
+ * const program = Effect.gen(function* () {
9218
+ * const config = yield* parse('{ "port": 3000 }');
9219
+ * return config;
9220
+ * });
9221
+ *
9222
+ * const result = Effect.runSync(program);
9223
+ * console.log(result); // { port: 3000 }
9224
+ * ```
9225
+ *
9226
+ * @privateRemarks
9227
+ * Uses {@link createScanner} internally with a recursive descent parser.
9228
+ * The scanner is created with `ignoreTrivia = false` so the parser can
9229
+ * report comment-related errors when `disallowComments` is set.
9230
+ *
9231
+ * @public
9232
+ */
9233
+ const parse$2 = (text, options) => Effect.sync(() => parseInternal(text, options ?? {}, false)).pipe(Effect.flatMap(({ value, errors }) => {
9234
+ if (errors.length > 0) return Effect.fail(new JsoncParseError({
9235
+ errors,
9236
+ text,
9237
+ ...options !== void 0 ? { options } : {}
9238
+ }));
9239
+ return Effect.succeed(value);
9240
+ }));
9241
+ function parseInternal(text, options, buildTree) {
9242
+ const scanner = createScanner$1(text, false);
9243
+ const errors = [];
9244
+ const disallowComments = options.disallowComments ?? false;
9245
+ const allowTrailingComma = options.allowTrailingComma ?? true;
9246
+ const allowEmptyContent = options.allowEmptyContent ?? false;
9247
+ let currentToken = "Unknown";
9248
+ function token() {
9249
+ return currentToken;
9250
+ }
9251
+ function scanNext() {
9252
+ for (;;) {
9253
+ currentToken = scanner.scan();
9254
+ switch (scanner.getTokenError()) {
9255
+ case "InvalidUnicode":
9256
+ handleError("InvalidUnicode");
9257
+ break;
9258
+ case "InvalidEscapeCharacter":
9259
+ handleError("InvalidEscapeCharacter");
9260
+ break;
9261
+ case "UnexpectedEndOfNumber":
9262
+ handleError("InvalidNumberFormat");
9263
+ break;
9264
+ case "UnexpectedEndOfComment":
9265
+ handleError("UnexpectedEndOfComment");
9266
+ break;
9267
+ case "UnexpectedEndOfString":
9268
+ handleError("UnexpectedEndOfString");
9269
+ break;
9270
+ case "InvalidCharacter":
9271
+ handleError("InvalidCharacter");
9272
+ break;
9273
+ }
9274
+ switch (currentToken) {
9275
+ case "LineComment":
9276
+ case "BlockComment":
9277
+ if (disallowComments) handleError("InvalidCommentToken");
9278
+ break;
9279
+ case "Trivia":
9280
+ case "LineBreak": break;
9281
+ default: return currentToken;
9282
+ }
9283
+ }
9284
+ }
9285
+ function tokenEnd() {
9286
+ return scanner.getTokenOffset() + scanner.getTokenLength();
9287
+ }
9288
+ function handleError(code, skipUntilAfter = [], skipUntil = []) {
9289
+ errors.push(new JsoncParseErrorDetail({
9290
+ code,
9291
+ message: formatError(code, scanner.getTokenOffset()),
9292
+ offset: scanner.getTokenOffset(),
9293
+ length: scanner.getTokenLength(),
9294
+ startLine: scanner.getTokenStartLine(),
9295
+ startCharacter: scanner.getTokenStartCharacter()
9296
+ }));
9297
+ if (skipUntilAfter.length > 0 || skipUntil.length > 0) {
9298
+ let t = token();
9299
+ while (t !== "EOF") {
9300
+ if (skipUntilAfter.includes(t)) {
9301
+ scanNext();
9302
+ break;
9303
+ }
9304
+ if (skipUntil.includes(t)) break;
9305
+ t = scanNext();
9306
+ }
9307
+ }
9308
+ }
9309
+ function parseValue() {
9310
+ switch (token()) {
9311
+ case "OpenBracket": return parseArray();
9312
+ case "OpenBrace": return parseObject();
9313
+ case "String": return parseString();
9314
+ case "Number": return parseNumber();
9315
+ case "True":
9316
+ scanNext();
9317
+ return true;
9318
+ case "False":
9319
+ scanNext();
9320
+ return false;
9321
+ case "Null":
9322
+ scanNext();
9323
+ return null;
9324
+ default: return;
9325
+ }
9326
+ }
9327
+ function parseString() {
9328
+ const value = scanner.getTokenValue();
9329
+ scanNext();
9330
+ return value;
9331
+ }
9332
+ function parseNumber() {
9333
+ const value = Number.parseFloat(scanner.getTokenValue());
9334
+ scanNext();
9335
+ return value;
9336
+ }
9337
+ function parseArray() {
9338
+ scanNext();
9339
+ const arr = [];
9340
+ let needsComma = false;
9341
+ while (token() !== "CloseBracket" && token() !== "EOF") {
9342
+ if (token() === "Comma") {
9343
+ if (!needsComma) handleError("ValueExpected");
9344
+ scanNext();
9345
+ if (token() === "CloseBracket" && allowTrailingComma) break;
9346
+ } else if (needsComma) handleError("CommaExpected");
9347
+ const value = parseValue();
9348
+ if (value === void 0) handleError("ValueExpected", [], ["CloseBracket", "Comma"]);
9349
+ else arr.push(value);
9350
+ needsComma = true;
9351
+ }
9352
+ if (token() !== "CloseBracket") handleError("CloseBracketExpected");
9353
+ else scanNext();
9354
+ return arr;
9355
+ }
9356
+ function parseObject() {
9357
+ scanNext();
9358
+ const obj = {};
9359
+ let needsComma = false;
9360
+ while (token() !== "CloseBrace" && token() !== "EOF") {
9361
+ if (token() === "Comma") {
9362
+ if (!needsComma) handleError("PropertyNameExpected");
9363
+ scanNext();
9364
+ if (token() === "CloseBrace" && allowTrailingComma) break;
9365
+ } else if (needsComma) handleError("CommaExpected");
9366
+ if (token() !== "String") {
9367
+ handleError("PropertyNameExpected", [], ["CloseBrace", "Comma"]);
9368
+ continue;
9369
+ }
9370
+ const key = scanner.getTokenValue();
9371
+ scanNext();
9372
+ if (token() !== "Colon") {
9373
+ handleError("ColonExpected", [], ["CloseBrace", "Comma"]);
9374
+ continue;
9375
+ }
9376
+ scanNext();
9377
+ const value = parseValue();
9378
+ if (value === void 0) handleError("ValueExpected", [], ["CloseBrace", "Comma"]);
9379
+ else obj[key] = value;
9380
+ needsComma = true;
9381
+ }
9382
+ if (token() !== "CloseBrace") handleError("CloseBraceExpected");
9383
+ else scanNext();
9384
+ return obj;
9385
+ }
9386
+ function parseValueTree() {
9387
+ switch (token()) {
9388
+ case "OpenBracket": return parseArrayTree();
9389
+ case "OpenBrace": return parseObjectTree();
9390
+ case "String": {
9391
+ const node = {
9392
+ type: "string",
9393
+ offset: scanner.getTokenOffset(),
9394
+ length: 0,
9395
+ value: scanner.getTokenValue()
9396
+ };
9397
+ const end = tokenEnd();
9398
+ scanNext();
9399
+ node.length = end - node.offset;
9400
+ return node;
9401
+ }
9402
+ case "Number": {
9403
+ const node = {
9404
+ type: "number",
9405
+ offset: scanner.getTokenOffset(),
9406
+ length: 0,
9407
+ value: Number.parseFloat(scanner.getTokenValue())
9408
+ };
9409
+ const end = tokenEnd();
9410
+ scanNext();
9411
+ node.length = end - node.offset;
9412
+ return node;
9413
+ }
9414
+ case "True": {
9415
+ const node = {
9416
+ type: "boolean",
9417
+ offset: scanner.getTokenOffset(),
9418
+ length: 0,
9419
+ value: true
9420
+ };
9421
+ const end = tokenEnd();
9422
+ scanNext();
9423
+ node.length = end - node.offset;
9424
+ return node;
9425
+ }
9426
+ case "False": {
9427
+ const node = {
9428
+ type: "boolean",
9429
+ offset: scanner.getTokenOffset(),
9430
+ length: 0,
9431
+ value: false
9432
+ };
9433
+ const end = tokenEnd();
9434
+ scanNext();
9435
+ node.length = end - node.offset;
9436
+ return node;
9437
+ }
9438
+ case "Null": {
9439
+ const node = {
9440
+ type: "null",
9441
+ offset: scanner.getTokenOffset(),
9442
+ length: 0,
9443
+ value: null
9444
+ };
9445
+ const end = tokenEnd();
9446
+ scanNext();
9447
+ node.length = end - node.offset;
9448
+ return node;
9449
+ }
9450
+ default: return;
9451
+ }
9452
+ }
9453
+ function parseArrayTree() {
9454
+ const node = {
9455
+ type: "array",
9456
+ offset: scanner.getTokenOffset(),
9457
+ length: 0,
9458
+ children: []
9459
+ };
9460
+ scanNext();
9461
+ let needsComma = false;
9462
+ while (token() !== "CloseBracket" && token() !== "EOF") {
9463
+ if (token() === "Comma") {
9464
+ if (!needsComma) handleError("ValueExpected");
9465
+ scanNext();
9466
+ if (token() === "CloseBracket" && allowTrailingComma) break;
9467
+ } else if (needsComma) handleError("CommaExpected");
9468
+ const child = parseValueTree();
9469
+ if (child) node.children.push(child);
9470
+ else handleError("ValueExpected", [], ["CloseBracket", "Comma"]);
9471
+ needsComma = true;
9472
+ }
9473
+ let end;
9474
+ if (token() !== "CloseBracket") {
9475
+ handleError("CloseBracketExpected");
9476
+ end = scanner.getTokenOffset();
9477
+ } else {
9478
+ end = tokenEnd();
9479
+ scanNext();
9480
+ }
9481
+ node.length = end - node.offset;
9482
+ return node;
9483
+ }
9484
+ function parseObjectTree() {
9485
+ const node = {
9486
+ type: "object",
9487
+ offset: scanner.getTokenOffset(),
9488
+ length: 0,
9489
+ children: []
9490
+ };
9491
+ scanNext();
9492
+ let needsComma = false;
9493
+ while (token() !== "CloseBrace" && token() !== "EOF") {
9494
+ if (token() === "Comma") {
9495
+ if (!needsComma) handleError("PropertyNameExpected");
9496
+ scanNext();
9497
+ if (token() === "CloseBrace" && allowTrailingComma) break;
9498
+ } else if (needsComma) handleError("CommaExpected");
9499
+ if (token() !== "String") {
9500
+ handleError("PropertyNameExpected", [], ["CloseBrace", "Comma"]);
9501
+ continue;
9502
+ }
9503
+ const property = {
9504
+ type: "property",
9505
+ offset: scanner.getTokenOffset(),
9506
+ length: 0,
9507
+ children: []
9508
+ };
9509
+ const keyNode = {
9510
+ type: "string",
9511
+ offset: scanner.getTokenOffset(),
9512
+ length: 0,
9513
+ value: scanner.getTokenValue()
9514
+ };
9515
+ const keyEnd = tokenEnd();
9516
+ scanNext();
9517
+ keyNode.length = keyEnd - keyNode.offset;
9518
+ property.children.push(keyNode);
9519
+ if (token() !== "Colon") {
9520
+ handleError("ColonExpected", [], ["CloseBrace", "Comma"]);
9521
+ property.length = scanner.getTokenOffset() - property.offset;
9522
+ node.children.push(property);
9523
+ continue;
9524
+ }
9525
+ property.colonOffset = scanner.getTokenOffset();
9526
+ scanNext();
9527
+ const valueNode = parseValueTree();
9528
+ if (valueNode) {
9529
+ property.children.push(valueNode);
9530
+ property.length = valueNode.offset + valueNode.length - property.offset;
9531
+ } else {
9532
+ handleError("ValueExpected", [], ["CloseBrace", "Comma"]);
9533
+ property.length = scanner.getTokenOffset() - property.offset;
9534
+ }
9535
+ node.children.push(property);
9536
+ needsComma = true;
9537
+ }
9538
+ let end;
9539
+ if (token() !== "CloseBrace") {
9540
+ handleError("CloseBraceExpected");
9541
+ end = scanner.getTokenOffset();
9542
+ } else {
9543
+ end = tokenEnd();
9544
+ scanNext();
9545
+ }
9546
+ node.length = end - node.offset;
9547
+ return node;
9548
+ }
9549
+ scanNext();
9550
+ if (buildTree) {
9551
+ const root = parseValueTree();
9552
+ if (token() !== "EOF") handleError("EndOfFileExpected");
9553
+ if (!root && !allowEmptyContent) handleError("ValueExpected");
9554
+ return {
9555
+ value: void 0,
9556
+ root,
9557
+ errors
9558
+ };
9559
+ }
9560
+ const value = parseValue();
9561
+ if (token() !== "EOF") handleError("EndOfFileExpected");
9562
+ if (value === void 0 && !allowEmptyContent) handleError("ValueExpected");
9563
+ return {
9564
+ value,
9565
+ root: void 0,
9566
+ errors
9567
+ };
9568
+ }
9569
+ function formatError(code, offset) {
9570
+ switch (code) {
9571
+ case "InvalidSymbol": return `Invalid symbol at offset ${offset}`;
9572
+ case "InvalidNumberFormat": return `Invalid number format at offset ${offset}`;
9573
+ case "PropertyNameExpected": return `Property name expected at offset ${offset}`;
9574
+ case "ValueExpected": return `Value expected at offset ${offset}`;
9575
+ case "ColonExpected": return `Colon expected at offset ${offset}`;
9576
+ case "CommaExpected": return `Comma expected at offset ${offset}`;
9577
+ case "CloseBraceExpected": return `Close brace expected at offset ${offset}`;
9578
+ case "CloseBracketExpected": return `Close bracket expected at offset ${offset}`;
9579
+ case "EndOfFileExpected": return `End of file expected at offset ${offset}`;
9580
+ case "InvalidCommentToken": return `Comments not allowed at offset ${offset}`;
9581
+ case "UnexpectedEndOfComment": return `Unexpected end of comment at offset ${offset}`;
9582
+ case "UnexpectedEndOfString": return `Unexpected end of string at offset ${offset}`;
9583
+ case "UnexpectedEndOfNumber": return `Unexpected end of number at offset ${offset}`;
9584
+ case "InvalidUnicode": return `Invalid unicode escape at offset ${offset}`;
9585
+ case "InvalidEscapeCharacter": return `Invalid escape character at offset ${offset}`;
9586
+ case "InvalidCharacter": return `Invalid character at offset ${offset}`;
9587
+ default: return `Parse error at offset ${offset}`;
9588
+ }
9589
+ }
9590
+
9591
+ //#endregion
9592
+ //#region ../../node_modules/.pnpm/jsonc-effect@0.3.1_effect@3.21.4/node_modules/jsonc-effect/format.js
9593
+ /**
9594
+ * Apply an array of text edits to JSONC source text.
9595
+ *
9596
+ * This is a {@link https://effect.website/docs/function-dual | Function.dual}
9597
+ * that supports both data-first and data-last (pipeline) usage.
9598
+ *
9599
+ * @param text - The original JSONC source text.
9600
+ * @param edits - A read-only array of {@link JsoncEdit} objects, typically
9601
+ * produced by {@link format} or {@link modify}.
9602
+ * @returns An `Effect` that succeeds with the edited string.
9603
+ *
9604
+ * @remarks
9605
+ * Edits are sorted in reverse offset order before application so that
9606
+ * earlier edits do not shift the offsets of later ones. The original `edits`
9607
+ * array is not mutated.
9608
+ *
9609
+ * @see {@link format} to compute formatting edits.
9610
+ * @see {@link modify} to compute structural edits (insert, replace, remove).
9611
+ *
9612
+ * @example Data-first usage
9613
+ * ```ts
9614
+ * import { Effect } from "effect";
9615
+ * import { format, applyEdits } from "jsonc-effect";
9616
+ *
9617
+ * const input = '{"a":1}';
9618
+ * const edits = Effect.runSync(format(input));
9619
+ * const result: string = Effect.runSync(applyEdits(input, edits));
9620
+ * ```
9621
+ *
9622
+ * @example Pipeline with modify
9623
+ * ```ts
9624
+ * import { Effect, pipe } from "effect";
9625
+ * import { modify, applyEdits } from "jsonc-effect";
9626
+ *
9627
+ * const input = '{ "a": 1 }';
9628
+ * const result = pipe(
9629
+ * input,
9630
+ * modify(["a"], 42),
9631
+ * Effect.flatMap((edits) => applyEdits(input, edits)),
9632
+ * Effect.runSync,
9633
+ * );
9634
+ * ```
9635
+ *
9636
+ * @public
9637
+ */
9638
+ const applyEdits$1 = Function.dual(2, (text, edits) => Effect.sync(() => {
9639
+ const sorted = [...edits].sort((a, b) => b.offset - a.offset);
9640
+ let result = text;
9641
+ for (const edit of sorted) result = result.substring(0, edit.offset) + edit.content + result.substring(edit.offset + edit.length);
9642
+ return result;
9643
+ }));
9644
+ /**
9645
+ * Compute edits to insert, replace, or remove a value at a JSON path.
9646
+ *
9647
+ * This is a {@link https://effect.website/docs/function-dual | Function.dual}
9648
+ * that supports both data-first and data-last (pipeline) usage.
9649
+ *
9650
+ * @param text - The JSONC source text to modify.
9651
+ * @param path - A {@link (JsoncPath:type)} (array of string keys and numeric indices)
9652
+ * identifying the target location in the JSON structure.
9653
+ * @param value - The value to set. Pass `undefined` to remove the
9654
+ * property or array element at the given path.
9655
+ * @param options - Optional object with `formattingOptions` controlling
9656
+ * indent size, tabs vs. spaces, and EOL style for generated text.
9657
+ * @returns An `Effect` that succeeds with a read-only array of
9658
+ * {@link JsoncEdit} objects, or fails with a
9659
+ * {@link JsoncModificationError} if the path cannot be navigated.
9660
+ *
9661
+ * @remarks
9662
+ * Setting `value` to `undefined` removes the targeted property or element,
9663
+ * including its surrounding comma. When inserting a new property into an
9664
+ * object, it is appended after the last existing property.
9665
+ *
9666
+ * @see {@link applyEdits} to apply the returned edits to the source text.
9667
+ * @see {@link JsoncModificationError} for the error type on navigation failure.
9668
+ *
9669
+ * @example Update an existing property
9670
+ * ```ts
9671
+ * import { Effect } from "effect";
9672
+ * import { modify, applyEdits } from "jsonc-effect";
9673
+ *
9674
+ * const input = '{ "a": 1 }';
9675
+ * const edits = Effect.runSync(modify(input, ["a"], 2));
9676
+ * const result: string = Effect.runSync(applyEdits(input, edits));
9677
+ * ```
9678
+ *
9679
+ * @example Insert a new property
9680
+ * ```ts
9681
+ * import { Effect } from "effect";
9682
+ * import { modify, applyEdits } from "jsonc-effect";
9683
+ *
9684
+ * const input = '{ "a": 1 }';
9685
+ * const edits = Effect.runSync(modify(input, ["b"], "hello"));
9686
+ * const result: string = Effect.runSync(applyEdits(input, edits));
9687
+ * ```
9688
+ *
9689
+ * @example Remove a property
9690
+ * ```ts
9691
+ * import { Effect } from "effect";
9692
+ * import { modify, applyEdits } from "jsonc-effect";
9693
+ *
9694
+ * const input = '{ "a": 1, "b": 2 }';
9695
+ * const edits = Effect.runSync(modify(input, ["a"], undefined));
9696
+ * const result: string = Effect.runSync(applyEdits(input, edits));
9697
+ * ```
9698
+ *
9699
+ * @example Pipeline (data-last) usage
9700
+ * ```ts
9701
+ * import { Effect, pipe } from "effect";
9702
+ * import { modify, applyEdits } from "jsonc-effect";
9703
+ *
9704
+ * const input = '{ "a": 1 }';
9705
+ * const result = pipe(
9706
+ * input,
9707
+ * modify(["a"], 42),
9708
+ * Effect.flatMap((edits) => applyEdits(input, edits)),
9709
+ * Effect.runSync,
9710
+ * );
9711
+ * ```
9712
+ *
9713
+ * @privateRemarks
9714
+ * Uses its own scanner-based navigation to locate the target path rather
9715
+ * than building a full AST via `parseTree`. This keeps the implementation
9716
+ * lightweight and avoids an intermediate allocation.
9717
+ *
9718
+ * @public
9719
+ */
9720
+ const modify = Function.dual((args) => typeof args[0] === "string" && Array.isArray(args[1]), (text, path, value, options) => Effect.try({
9721
+ try: () => modifyImpl(text, path, value, options?.formattingOptions),
9722
+ catch: (e) => new JsoncModificationError({
9723
+ path,
9724
+ reason: String(e)
9725
+ })
9726
+ }));
9727
+ function modifyImpl(text, path, value, formattingOptions) {
9728
+ const opts = {
9729
+ tabSize: formattingOptions?.tabSize ?? 2,
9730
+ insertSpaces: formattingOptions?.insertSpaces ?? true,
9731
+ eol: formattingOptions?.eol ?? "\n"
9732
+ };
9733
+ const indentUnit = opts.insertSpaces ? " ".repeat(opts.tabSize) : " ";
9734
+ if (path.length === 0) {
9735
+ const content = value === void 0 ? "" : JSON.stringify(value, null, opts.tabSize);
9736
+ return [{
9737
+ offset: 0,
9738
+ length: text.length,
9739
+ content
9740
+ }];
9741
+ }
9742
+ const scanner = createScanner$1(text, true);
9743
+ let currentToken = scanner.scan();
9744
+ function tokenEnd() {
9745
+ return scanner.getTokenOffset() + scanner.getTokenLength();
9746
+ }
9747
+ function skipValue() {
9748
+ switch (currentToken) {
9749
+ case "OpenBrace": {
9750
+ let end = tokenEnd();
9751
+ currentToken = scanner.scan();
9752
+ let first = true;
9753
+ while (currentToken !== "CloseBrace" && currentToken !== "EOF") {
9754
+ if (!first && currentToken === "Comma") currentToken = scanner.scan();
9755
+ if (currentToken === "String") {
9756
+ currentToken = scanner.scan();
9757
+ if (currentToken === "Colon") {
9758
+ currentToken = scanner.scan();
9759
+ end = skipValue();
9760
+ }
9761
+ } else {
9762
+ end = tokenEnd();
9763
+ currentToken = scanner.scan();
9764
+ }
9765
+ first = false;
9766
+ }
9767
+ if (currentToken === "CloseBrace") {
9768
+ end = tokenEnd();
9769
+ currentToken = scanner.scan();
9770
+ }
9771
+ return end;
9772
+ }
9773
+ case "OpenBracket": {
9774
+ let end = tokenEnd();
9775
+ currentToken = scanner.scan();
9776
+ let first = true;
9777
+ while (currentToken !== "CloseBracket" && currentToken !== "EOF") {
9778
+ if (!first && currentToken === "Comma") currentToken = scanner.scan();
9779
+ end = skipValue();
9780
+ first = false;
9781
+ }
9782
+ if (currentToken === "CloseBracket") {
9783
+ end = tokenEnd();
9784
+ currentToken = scanner.scan();
9785
+ }
9786
+ return end;
9787
+ }
9788
+ default: {
9789
+ const end = tokenEnd();
9790
+ currentToken = scanner.scan();
9791
+ return end;
9792
+ }
9793
+ }
9794
+ }
9795
+ let depth = 0;
9796
+ for (const segment of path) {
9797
+ depth++;
9798
+ if (typeof segment === "string") {
9799
+ if (currentToken !== "OpenBrace") throw new Error(`Expected object at depth ${depth}`);
9800
+ currentToken = scanner.scan();
9801
+ let found = false;
9802
+ let lastValueEnd = scanner.getTokenOffset();
9803
+ let isFirst = true;
9804
+ while (currentToken !== "CloseBrace" && currentToken !== "EOF") {
9805
+ if (!isFirst && currentToken === "Comma") currentToken = scanner.scan();
9806
+ if (currentToken === "String") {
9807
+ const key = scanner.getTokenValue();
9808
+ currentToken = scanner.scan();
9809
+ if (currentToken === "Colon") currentToken = scanner.scan();
9810
+ if (key === segment) {
9811
+ found = true;
9812
+ if (depth === path.length) {
9813
+ const valueStart = scanner.getTokenOffset();
9814
+ const prevEnd = valueStart;
9815
+ const valueEnd = skipValue();
9816
+ if (value === void 0) {
9817
+ let removeStart = valueStart;
9818
+ let removeEnd = valueEnd;
9819
+ const keyStart = text.substring(0, valueStart).lastIndexOf(`"${segment}"`);
9820
+ if (keyStart >= 0) removeStart = keyStart;
9821
+ const commaPosBefore = text.substring(0, removeStart).trimEnd().lastIndexOf(",");
9822
+ if (commaPosBefore >= 0) removeStart = commaPosBefore;
9823
+ else {
9824
+ const trimmedAfter = text.substring(removeEnd).match(/^(\s*,)/);
9825
+ if (trimmedAfter) removeEnd += trimmedAfter[1].length;
9826
+ }
9827
+ return [{
9828
+ offset: removeStart,
9829
+ length: removeEnd - removeStart,
9830
+ content: ""
9831
+ }];
9832
+ }
9833
+ const serialized = JSON.stringify(value, null, opts.tabSize);
9834
+ return [{
9835
+ offset: prevEnd,
9836
+ length: valueEnd - prevEnd,
9837
+ content: serialized
9838
+ }];
9839
+ }
9840
+ break;
9841
+ }
9842
+ lastValueEnd = skipValue();
9843
+ } else {
9844
+ currentToken = scanner.scan();
9845
+ lastValueEnd = scanner.getTokenOffset();
9846
+ }
9847
+ isFirst = false;
9848
+ }
9849
+ if (!found && depth === path.length && value !== void 0) {
9850
+ const indent = indentUnit.repeat(depth);
9851
+ const serialized = JSON.stringify(value, null, opts.tabSize);
9852
+ const insertText = isFirst ? `${opts.eol}${indent}"${segment}": ${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}"${segment}": ${serialized}`;
9853
+ return [{
9854
+ offset: lastValueEnd,
9855
+ length: 0,
9856
+ content: insertText
9857
+ }];
9858
+ }
9859
+ } else {
9860
+ if (currentToken !== "OpenBracket") throw new Error(`Expected array at depth ${depth}`);
9861
+ currentToken = scanner.scan();
9862
+ let idx = 0;
9863
+ let lastEnd = scanner.getTokenOffset();
9864
+ while (currentToken !== "CloseBracket" && currentToken !== "EOF") {
9865
+ if (idx > 0 && currentToken === "Comma") currentToken = scanner.scan();
9866
+ if (idx === segment) {
9867
+ if (depth === path.length) {
9868
+ const valueStart = scanner.getTokenOffset();
9869
+ const valueEnd = skipValue();
9870
+ if (value === void 0) {
9871
+ let removeEnd = valueEnd;
9872
+ if (text.substring(removeEnd).trimStart().startsWith(",")) removeEnd = text.indexOf(",", removeEnd) + 1;
9873
+ return [{
9874
+ offset: valueStart,
9875
+ length: removeEnd - valueStart,
9876
+ content: ""
9877
+ }];
9878
+ }
9879
+ const serialized = JSON.stringify(value, null, opts.tabSize);
9880
+ return [{
9881
+ offset: valueStart,
9882
+ length: valueEnd - valueStart,
9883
+ content: serialized
9884
+ }];
9885
+ }
9886
+ break;
9887
+ }
9888
+ lastEnd = skipValue();
9889
+ idx++;
9890
+ }
9891
+ if (idx <= segment && depth === path.length && value !== void 0) {
9892
+ const indent = indentUnit.repeat(depth);
9893
+ const serialized = JSON.stringify(value, null, opts.tabSize);
9894
+ const insertText = idx === 0 ? `${opts.eol}${indent}${serialized}${opts.eol}${indentUnit.repeat(depth - 1)}` : `,${opts.eol}${indent}${serialized}`;
9895
+ return [{
9896
+ offset: lastEnd,
9897
+ length: 0,
9898
+ content: insertText
9899
+ }];
9900
+ }
9901
+ }
9902
+ }
9903
+ return [];
9904
+ }
9905
+
8611
9906
  //#endregion
8612
9907
  //#region ../../node_modules/.pnpm/workspaces-effect@2.0.2_@effect+platform@0.96.2_effect@3.21.4__effect@3.21.4/node_modules/workspaces-effect/schemas/WorkspaceStateSnapshot.js
8613
9908
  const DepRecord = Schema.optionalWith(Schema.Record({
@@ -8899,7 +10194,7 @@ const packageSnapshotFromJson = (text, relativePath) => {
8899
10194
  *
8900
10195
  * @internal
8901
10196
  */
8902
- const lockfileCatalogsAtRef = (text) => Option.isNone(text) ? Effect.succeed(CatalogSet.empty()) : parse$2(text.value).pipe(Effect.map((parsed) => CatalogSet.fromLockfileCatalogs(parsed?.catalogs)), Effect.orElseSucceed(() => CatalogSet.empty()));
10197
+ const lockfileCatalogsAtRef = (text) => Option.isNone(text) ? Effect.succeed(CatalogSet.empty()) : parse$3(text.value).pipe(Effect.map((parsed) => CatalogSet.fromLockfileCatalogs(parsed?.catalogs)), Effect.orElseSucceed(() => CatalogSet.empty()));
8903
10198
  /**
8904
10199
  * Live layer for the {@link PointInTimeWorkspace} service.
8905
10200
  *
@@ -37887,15 +39182,16 @@ function makeShape$2(inspector) {
37887
39182
  path,
37888
39183
  status: "added"
37889
39184
  }));
39185
+ const isOwnChangeset = (path) => path.startsWith(".changeset/") && path.endsWith(".md");
37890
39186
  const seen = /* @__PURE__ */ new Set();
37891
39187
  const rawEntries = [];
37892
39188
  for (const e of diffEntries) {
37893
- if (seen.has(e.path)) continue;
39189
+ if (seen.has(e.path) || isOwnChangeset(e.path)) continue;
37894
39190
  seen.add(e.path);
37895
39191
  rawEntries.push(e);
37896
39192
  }
37897
39193
  for (const e of untrackedEntries) {
37898
- if (seen.has(e.path)) continue;
39194
+ if (seen.has(e.path) || isOwnChangeset(e.path)) continue;
37899
39195
  seen.add(e.path);
37900
39196
  rawEntries.push(e);
37901
39197
  }
@@ -38030,6 +39326,32 @@ const DEP_TYPE_MAP = [
38030
39326
  */
38031
39327
  const resolveOrRaw = (snapshot, dep, spec) => Option.getOrElse(snapshot.resolve(dep, spec), () => spec);
38032
39328
  /**
39329
+ * Drop no-net-change field moves: the same dependency removed from one field
39330
+ * and added to another with an equal resolved version (e.g. a dep promoted
39331
+ * from `devDependencies` to `dependencies`). A field reclassification is a
39332
+ * contract change worth release-note prose, not a version movement, so it
39333
+ * must not surface as an unrelated removed row plus an added row. Moves that
39334
+ * also change the resolved version keep both rows (the movement is real).
39335
+ */
39336
+ const collapseFieldMoves = (rows) => {
39337
+ const dropped = /* @__PURE__ */ new Set();
39338
+ const byName = /* @__PURE__ */ new Map();
39339
+ for (const row of rows) {
39340
+ const group = byName.get(row.dependency);
39341
+ if (group) group.push(row);
39342
+ else byName.set(row.dependency, [row]);
39343
+ }
39344
+ for (const group of byName.values()) for (const removed of group) {
39345
+ if (removed.action !== "removed" || dropped.has(removed)) continue;
39346
+ const added = group.find((r) => r.action === "added" && !dropped.has(r) && r.type !== removed.type && r.to === removed.from);
39347
+ if (added) {
39348
+ dropped.add(removed);
39349
+ dropped.add(added);
39350
+ }
39351
+ }
39352
+ return rows.filter((r) => !dropped.has(r));
39353
+ };
39354
+ /**
38033
39355
  * Diff two workspace snapshots and return per-package dependency-table rows,
38034
39356
  * comparing already-resolved specifier values per side.
38035
39357
  *
@@ -38085,10 +39407,11 @@ function computeWorkspaceDependencyDiffs(before, after) {
38085
39407
  });
38086
39408
  }
38087
39409
  }
38088
- if (rows.length > 0) result.push({
39410
+ const collapsed = collapseFieldMoves(rows);
39411
+ if (collapsed.length > 0) result.push({
38089
39412
  package: afterPkg.name,
38090
39413
  relativePath: afterPkg.relativePath,
38091
- rows: sortDependencyRows(rows)
39414
+ rows: sortDependencyRows(collapsed)
38092
39415
  });
38093
39416
  }
38094
39417
  return result;
@@ -38431,7 +39754,8 @@ function makeShape$1(pit, inspector, discovery, detector, config, fs) {
38431
39754
  fromRef = yield* gitMergeBase(resolvedCwd, baseBranch);
38432
39755
  }
38433
39756
  const rawDiffs = computeWorkspaceDependencyDiffs(yield* pit.at(fromRef, { cwd: resolvedCwd }), options.to ? yield* pit.at(options.to, { cwd: resolvedCwd }) : yield* pit.worktree({ cwd: resolvedCwd }));
38434
- const targetPkg = options.package;
39757
+ const explicitTargets = /* @__PURE__ */ new Set([...options.packages ?? [], ...options.package ? [options.package] : []]);
39758
+ const excluded = new Set(options.exclude ?? []);
38435
39759
  const livePackages = yield* discovery.listPackages(resolvedCwd);
38436
39760
  const publishable = yield* listPublishablePackageNames(livePackages, resolvedCwd).pipe(Effect.provide(provideDetector));
38437
39761
  const versionPrivate = yield* config.versionPrivate(resolvedCwd);
@@ -38440,9 +39764,15 @@ function makeShape$1(pit, inspector, discovery, detector, config, fs) {
38440
39764
  if (yield* config.isIgnored(pkg.name, resolvedCwd)) continue;
38441
39765
  if (publishable.has(pkg.name) || versionPrivate) inScope.add(pkg.name);
38442
39766
  }
38443
- const targetIgnored = targetPkg ? yield* config.isIgnored(targetPkg, resolvedCwd) : false;
39767
+ const activeTargets = /* @__PURE__ */ new Set();
39768
+ for (const name of explicitTargets) {
39769
+ if (excluded.has(name)) continue;
39770
+ if (yield* config.isIgnored(name, resolvedCwd)) continue;
39771
+ activeTargets.add(name);
39772
+ }
39773
+ const inScopeFor = (name) => explicitTargets.size > 0 ? activeTargets.has(name) : inScope.has(name) && !excluded.has(name);
38444
39774
  const keepDevDeps = options.includeDevDeps === true;
38445
- const scoped = targetPkg ? targetIgnored ? [] : rawDiffs.filter((d) => d.package === targetPkg) : rawDiffs.filter((d) => inScope.has(d.package));
39775
+ const scoped = rawDiffs.filter((d) => inScopeFor(d.package));
38446
39776
  const resolved = [];
38447
39777
  for (const diff of scoped) {
38448
39778
  const rows = keepDevDeps ? [...diff.rows] : diff.rows.filter((r) => r.type !== "devDependency");
@@ -38453,7 +39783,7 @@ function makeShape$1(pit, inspector, discovery, detector, config, fs) {
38453
39783
  }
38454
39784
  const existingPure = yield* findPureDependencyChangesets(fs, changesetDir);
38455
39785
  const skippedMixed = yield* findMixedDependencyChangesets(fs, changesetDir);
38456
- const toDelete = targetPkg ? targetIgnored ? [] : existingPure.filter((p) => p.package === targetPkg) : existingPure.filter((p) => inScope.has(p.package));
39786
+ const toDelete = existingPure.filter((p) => inScopeFor(p.package));
38457
39787
  const chosenFilenames = /* @__PURE__ */ new Set();
38458
39788
  const toWrite = [];
38459
39789
  for (const diff of resolved) {
@@ -38671,23 +40001,46 @@ function parseJsonPath(path) {
38671
40001
  * @internal
38672
40002
  */
38673
40003
  function jsonPathGet(obj, path) {
40004
+ return walkJsonPath(obj, path).map((entry) => entry.node);
40005
+ }
40006
+ /**
40007
+ * Shared breadth-first traversal behind {@link jsonPathGet} and
40008
+ * {@link jsonPathResolve}: each segment fans out the current set of matched
40009
+ * entries, carrying both the node and the concrete path taken to reach it.
40010
+ * The two public functions differ only in which half of the entry they keep.
40011
+ */
40012
+ function walkJsonPath(obj, path) {
38674
40013
  const segments = parseJsonPath(path);
38675
- let current = [obj];
40014
+ let current = [{
40015
+ node: obj,
40016
+ path: []
40017
+ }];
38676
40018
  for (const segment of segments) {
38677
40019
  const next = [];
38678
- for (const node of current) {
40020
+ for (const { node, path: nodePath } of current) {
38679
40021
  if (node === null || node === void 0 || typeof node !== "object") continue;
38680
40022
  switch (segment.type) {
38681
40023
  case "property": {
38682
40024
  const value = node[segment.key];
38683
- if (value !== void 0) next.push(value);
40025
+ if (value !== void 0) next.push({
40026
+ node: value,
40027
+ path: [...nodePath, segment.key]
40028
+ });
38684
40029
  break;
38685
40030
  }
38686
40031
  case "index":
38687
- if (Array.isArray(node) && segment.index < node.length) next.push(node[segment.index]);
40032
+ if (Array.isArray(node) && segment.index < node.length) next.push({
40033
+ node: node[segment.index],
40034
+ path: [...nodePath, segment.index]
40035
+ });
38688
40036
  break;
38689
40037
  case "wildcard":
38690
- if (Array.isArray(node)) next.push(...node);
40038
+ if (Array.isArray(node)) node.forEach((element, index) => {
40039
+ next.push({
40040
+ node: element,
40041
+ path: [...nodePath, index]
40042
+ });
40043
+ });
38691
40044
  break;
38692
40045
  }
38693
40046
  }
@@ -38696,81 +40049,37 @@ function jsonPathGet(obj, path) {
38696
40049
  return current;
38697
40050
  }
38698
40051
  /**
38699
- * Mutate all matching locations in an object in-place.
40052
+ * Resolve a JSONPath expression to the concrete paths of every existing match.
38700
40053
  *
38701
40054
  * @remarks
38702
- * Walks to the parent(s) of the final segment, then sets the value
38703
- * at each matching location. Only updates existing keys/indices;
38704
- * does not create new properties or extend arrays. Returns the count
38705
- * of locations actually updated.
40055
+ * Uses the same breadth-first expansion as {@link jsonPathGet}, but instead of
40056
+ * collecting the matched *values* it records the concrete `(string | number)[]`
40057
+ * path taken to reach each one. Wildcards and indices are materialized into the
40058
+ * numeric array index actually traversed, so the returned paths are directly
40059
+ * consumable by structural editors such as `jsonc-effect`'s `modify`, which
40060
+ * require a fully concrete path (no wildcards).
38706
40061
  *
38707
- * @param obj - The object to modify in-place
40062
+ * Only existing locations are returned; nothing is created. A path with no
40063
+ * matches yields an empty array, and the empty path (`"$."`) yields a single
40064
+ * empty concrete path (the document root).
40065
+ *
40066
+ * @param obj - The object to query
38708
40067
  * @param path - JSONPath string (e.g., `"$.packages[*].version"`)
38709
- * @param value - The value to set at each matching location
38710
- * @returns The number of locations updated (0 if no matches or empty path)
40068
+ * @returns Array of concrete paths, each an array of string keys / numeric indices
38711
40069
  *
38712
40070
  * @example
38713
40071
  * ```typescript
38714
- * import { jsonPathSet } from "../utils/jsonpath.js";
40072
+ * import { jsonPathResolve } from "../utils/jsonpath.js";
38715
40073
  *
38716
- * const obj = { version: "1.0.0" };
38717
- * const count = jsonPathSet(obj, "$.version", "2.0.0");
38718
- * // count === 1, obj.version === "2.0.0"
40074
+ * const obj = { packages: [{ version: "1.0.0" }, { version: "2.0.0" }] };
40075
+ * const paths = jsonPathResolve(obj, "$.packages[*].version");
40076
+ * // [["packages", 0, "version"], ["packages", 1, "version"]]
38719
40077
  * ```
38720
40078
  *
38721
40079
  * @internal
38722
40080
  */
38723
- function jsonPathSet(obj, path, value) {
38724
- const segments = parseJsonPath(path);
38725
- if (segments.length === 0) return 0;
38726
- const lastSegment = segments[segments.length - 1];
38727
- const parentSegments = segments.slice(0, -1);
38728
- let parents = [obj];
38729
- for (const segment of parentSegments) {
38730
- const next = [];
38731
- for (const node of parents) {
38732
- if (node === null || node === void 0 || typeof node !== "object") continue;
38733
- switch (segment.type) {
38734
- case "property": {
38735
- const child = node[segment.key];
38736
- if (child !== void 0) next.push(child);
38737
- break;
38738
- }
38739
- case "index":
38740
- if (Array.isArray(node) && segment.index < node.length) next.push(node[segment.index]);
38741
- break;
38742
- case "wildcard":
38743
- if (Array.isArray(node)) next.push(...node);
38744
- break;
38745
- }
38746
- }
38747
- parents = next;
38748
- }
38749
- let count = 0;
38750
- for (const parent of parents) {
38751
- if (parent === null || parent === void 0 || typeof parent !== "object") continue;
38752
- switch (lastSegment.type) {
38753
- case "property":
38754
- if (lastSegment.key in parent) {
38755
- parent[lastSegment.key] = value;
38756
- count++;
38757
- }
38758
- break;
38759
- case "index":
38760
- if (Array.isArray(parent) && lastSegment.index < parent.length) {
38761
- parent[lastSegment.index] = value;
38762
- count++;
38763
- }
38764
- break;
38765
- case "wildcard":
38766
- if (Array.isArray(parent)) for (let i = 0; i < parent.length; i++) {
38767
- parent[i] = value;
38768
- count++;
38769
- }
38770
- break;
38771
- }
38772
- }
38773
- return count;
40081
+ function jsonPathResolve(obj, path) {
40082
+ return walkJsonPath(obj, path).map((entry) => entry.path);
38774
40083
  }
38775
40084
 
38776
40085
  //#endregion
@@ -38962,31 +40271,39 @@ var VersionFiles = class VersionFiles {
38962
40271
  return content.match(/^(\s+)"/m)?.[1] ?? " ";
38963
40272
  }
38964
40273
  /**
38965
- * Update JSON file at specified JSONPath locations.
40274
+ * Update a JSON (or JSONC) file at specified JSONPath locations,
40275
+ * preserving the original formatting byte-for-byte.
38966
40276
  *
38967
40277
  * @remarks
38968
- * Reads the file, detects its indentation style and trailing newline
38969
- * preference, applies all JSONPath updates via {@link jsonPathSet},
38970
- * and writes the result back preserving the original formatting.
38971
- * Returns `undefined` if no JSONPath locations matched (no write occurs).
40278
+ * The write is performed with `jsonc-effect`'s format-preserving
40279
+ * `modify`/`applyEdits` rather than a `JSON.parse`/`JSON.stringify`
40280
+ * round-trip (which always explodes inline arrays one-element-per-line and
40281
+ * drops comments). Each JSONPath expression is resolved to concrete
40282
+ * `(string | number)[]` paths against the parsed document, and each
40283
+ * concrete path becomes a minimal text edit that touches only the target
40284
+ * value's span — so inline arrays, comments,
40285
+ * indentation, and the trailing-newline preference all survive; a one-line
40286
+ * version bump produces a one-line diff.
40287
+ *
40288
+ * Insertion semantics: a concrete, wildcard-free JSONPath whose leaf
40289
+ * property does not exist yet is inserted after the last sibling using the
40290
+ * document's detected indent (the one case where indent detection still
40291
+ * matters). Wildcard expressions only ever update existing matches. Returns
40292
+ * `undefined` (no write) when nothing was updated or inserted.
38972
40293
  *
38973
40294
  * @param filePath - Absolute path to the JSON file
38974
40295
  * @param jsonPaths - JSONPath expressions to update
38975
40296
  * @param version - New version string
38976
40297
  * @returns Update result, or `undefined` if no changes were made
40298
+ *
40299
+ * @see {@link jsonPathResolve} for concrete-path enumeration
40300
+ * @see {@link VersionFiles.applyVersionEdit} for the per-path edit
38977
40301
  */
38978
40302
  static updateFile(filePath, jsonPaths, version) {
38979
- const content = readFileSync(filePath, "utf-8");
38980
- const indent = VersionFiles.detectIndent(content);
38981
- const trailingNewline = content.endsWith("\n");
38982
- const obj = JSON.parse(content);
38983
- const previousValues = jsonPaths.flatMap((jp) => jsonPathGet(obj, jp));
38984
- let totalUpdated = 0;
38985
- for (const jp of jsonPaths) totalUpdated += jsonPathSet(obj, jp, version);
38986
- if (totalUpdated === 0) return;
38987
- let output = JSON.stringify(obj, null, indent);
38988
- if (trailingNewline) output += "\n";
38989
- writeFileSync(filePath, output, "utf-8");
40303
+ const original = readFileSync(filePath, "utf-8");
40304
+ const { content, previousValues, totalChanged } = VersionFiles.computeUpdate(original, jsonPaths, version);
40305
+ if (totalChanged === 0) return;
40306
+ writeFileSync(filePath, content, "utf-8");
38990
40307
  return {
38991
40308
  filePath,
38992
40309
  jsonPaths,
@@ -38995,6 +40312,83 @@ var VersionFiles = class VersionFiles {
38995
40312
  };
38996
40313
  }
38997
40314
  /**
40315
+ * Compute the full update for a document without touching the filesystem:
40316
+ * the edited content, the previous values at every matched path, and how
40317
+ * many locations actually changed.
40318
+ *
40319
+ * @remarks
40320
+ * This is the single decision path shared by {@link VersionFiles.updateFile}
40321
+ * and the dry-run branches of the two process methods, so a preview reports
40322
+ * exactly the files a real run would write — including pending inserts of a
40323
+ * not-yet-existing wildcard-free leaf, and excluding same-value no-ops.
40324
+ *
40325
+ * @param original - Document text as read from disk
40326
+ * @param jsonPaths - JSONPath expressions to update
40327
+ * @param version - New version string
40328
+ * @returns The updated content, previous values, and changed-location count
40329
+ */
40330
+ static computeUpdate(original, jsonPaths, version) {
40331
+ let content = original;
40332
+ const obj = Effect.runSync(parse$2(content));
40333
+ const previousValues = jsonPaths.flatMap((jp) => jsonPathGet(obj, jp));
40334
+ const indent = VersionFiles.detectIndent(content);
40335
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
40336
+ let totalChanged = 0;
40337
+ for (const jp of jsonPaths) {
40338
+ const segments = parseJsonPath(jp);
40339
+ if (segments.length === 0) continue;
40340
+ const hasWildcard = segments.some((segment) => segment.type === "wildcard");
40341
+ let concretePaths;
40342
+ if (hasWildcard) concretePaths = jsonPathResolve(obj, jp);
40343
+ else {
40344
+ const direct = segments.map((segment) => segment.type === "property" ? segment.key : segment.index);
40345
+ concretePaths = jsonPathResolve(obj, jp).length > 0 || typeof direct[direct.length - 1] === "string" ? [direct] : [];
40346
+ }
40347
+ for (const concretePath of concretePaths) {
40348
+ const updated = VersionFiles.applyVersionEdit(content, concretePath, version, indent, eol);
40349
+ if (updated !== void 0) {
40350
+ content = updated;
40351
+ totalChanged += 1;
40352
+ }
40353
+ }
40354
+ }
40355
+ return {
40356
+ content,
40357
+ previousValues,
40358
+ totalChanged
40359
+ };
40360
+ }
40361
+ /**
40362
+ * Compute the format-preserving edit for a single concrete path, returning
40363
+ * the updated document, or `undefined` when nothing changed.
40364
+ *
40365
+ * @remarks
40366
+ * Delegates to `jsonc-effect`'s {@link modify} + {@link applyEdits}
40367
+ * (requires `jsonc-effect >= 0.3.1`, whose edit spans touch only the target
40368
+ * value), so every other byte of the document is preserved. When the leaf
40369
+ * of a wildcard-free path does not exist, `modify` inserts the property
40370
+ * after the last sibling using the supplied formatting options — the only
40371
+ * case where the detected indent matters. A path whose parent is missing or
40372
+ * not an object cannot be navigated; the resulting modification error is
40373
+ * caught and reported as "no change" so the file is left alone.
40374
+ *
40375
+ * @param content - Current document text
40376
+ * @param concretePath - A wildcard-free `(string | number)[]` path
40377
+ * @param version - New version string
40378
+ * @param indentUnit - One indentation level, for inserted text
40379
+ * @param eol - End-of-line sequence, for inserted text
40380
+ * @returns The updated document, or `undefined` if the path was unchanged
40381
+ */
40382
+ static applyVersionEdit(content, concretePath, version, indentUnit, eol) {
40383
+ const insertSpaces = !indentUnit.includes(" ");
40384
+ const program = modify(content, [...concretePath], version, { formattingOptions: {
40385
+ insertSpaces,
40386
+ tabSize: insertSpaces ? indentUnit.length : 1,
40387
+ eol
40388
+ } }).pipe(Effect.flatMap((edits) => applyEdits$1(content, edits)), Effect.map((updated) => updated === content ? void 0 : updated), Effect.catchTag("JsoncModificationError", () => Effect.succeed(void 0)));
40389
+ return Effect.runSync(program);
40390
+ }
40391
+ /**
38998
40392
  * Orchestrate the full version file update flow.
38999
40393
  *
39000
40394
  * @remarks
@@ -39020,9 +40414,8 @@ var VersionFiles = class VersionFiles {
39020
40414
  try {
39021
40415
  if (dryRun) {
39022
40416
  const content = readFileSync(filePath, "utf-8");
39023
- const obj = JSON.parse(content);
39024
- const previousValues = jsonPaths.flatMap((jp) => jsonPathGet(obj, jp));
39025
- if (previousValues.length > 0) updates.push({
40417
+ const { previousValues, totalChanged } = VersionFiles.computeUpdate(content, jsonPaths, version);
40418
+ if (totalChanged > 0) updates.push({
39026
40419
  filePath,
39027
40420
  jsonPaths,
39028
40421
  version,
@@ -39066,9 +40459,8 @@ var VersionFiles = class VersionFiles {
39066
40459
  for (const filePath of vf.matchedFiles) try {
39067
40460
  if (dryRun) {
39068
40461
  const content = readFileSync(filePath, "utf-8");
39069
- const obj = JSON.parse(content);
39070
- const previousValues = jsonPaths.flatMap((jp) => jsonPathGet(obj, jp));
39071
- if (previousValues.length > 0) updates.push({
40462
+ const { previousValues, totalChanged } = VersionFiles.computeUpdate(content, jsonPaths, scope.version);
40463
+ if (totalChanged > 0) updates.push({
39072
40464
  filePath,
39073
40465
  jsonPaths,
39074
40466
  version: scope.version,