@descent-vtt/spec-harness 0.5.0 → 0.7.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 (84) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +148 -0
  4. package/README.md +273 -24
  5. package/dist/audit.d.ts +71 -1
  6. package/dist/audit.js +140 -17
  7. package/dist/audit.js.map +1 -1
  8. package/dist/branch.d.ts +13 -0
  9. package/dist/branch.js +19 -0
  10. package/dist/branch.js.map +1 -1
  11. package/dist/cli.d.ts +1 -1
  12. package/dist/cli.js +51 -13
  13. package/dist/cli.js.map +1 -1
  14. package/dist/commands.js +13 -4
  15. package/dist/commands.js.map +1 -1
  16. package/dist/config.js +3 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/configure.d.ts +50 -3
  19. package/dist/configure.js +78 -5
  20. package/dist/configure.js.map +1 -1
  21. package/dist/context.d.ts +21 -7
  22. package/dist/context.js +104 -31
  23. package/dist/context.js.map +1 -1
  24. package/dist/document.d.ts +2 -1
  25. package/dist/document.js.map +1 -1
  26. package/dist/formats.d.ts +31 -0
  27. package/dist/formats.js +127 -0
  28. package/dist/formats.js.map +1 -0
  29. package/dist/git.d.ts +6 -0
  30. package/dist/git.js +8 -0
  31. package/dist/git.js.map +1 -1
  32. package/dist/guard.d.ts +8 -0
  33. package/dist/guard.js +19 -1
  34. package/dist/guard.js.map +1 -1
  35. package/dist/host.d.ts +14 -0
  36. package/dist/host.js +50 -0
  37. package/dist/host.js.map +1 -0
  38. package/dist/index.d.ts +5 -3
  39. package/dist/index.js +2 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/manifests.d.ts +8 -1
  42. package/dist/manifests.js +16 -6
  43. package/dist/manifests.js.map +1 -1
  44. package/dist/premises.d.ts +3 -0
  45. package/dist/premises.js +43 -13
  46. package/dist/premises.js.map +1 -1
  47. package/dist/reader.js +10 -2
  48. package/dist/reader.js.map +1 -1
  49. package/dist/round.d.ts +8 -0
  50. package/dist/round.js +73 -22
  51. package/dist/round.js.map +1 -1
  52. package/dist/server.js +11 -3
  53. package/dist/server.js.map +1 -1
  54. package/dist/setup.d.ts +10 -0
  55. package/dist/setup.js +68 -32
  56. package/dist/setup.js.map +1 -1
  57. package/dist/signers.d.ts +43 -0
  58. package/dist/signers.js +105 -0
  59. package/dist/signers.js.map +1 -0
  60. package/dist/types.d.ts +7 -0
  61. package/dist/types.js.map +1 -1
  62. package/dist/vendor/spec-core/markdown/frontmatter.d.ts +11 -1
  63. package/dist/vendor/spec-core/markdown/frontmatter.js +20 -3
  64. package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -1
  65. package/dist/vendor/spec-core/pattern/glob.d.ts +88 -2
  66. package/dist/vendor/spec-core/pattern/glob.js +234 -32
  67. package/dist/vendor/spec-core/pattern/glob.js.map +1 -1
  68. package/dist/vendor/spec-core/pattern/index.d.ts +2 -2
  69. package/dist/vendor/spec-core/pattern/index.js +1 -1
  70. package/dist/vendor/spec-core/pattern/index.js.map +1 -1
  71. package/dist/vendor/spec-core/text/index.d.ts +1 -0
  72. package/dist/vendor/spec-core/text/index.js +1 -0
  73. package/dist/vendor/spec-core/text/index.js.map +1 -1
  74. package/dist/vendor/spec-core/text/width.d.ts +30 -0
  75. package/dist/vendor/spec-core/text/width.js +113 -0
  76. package/dist/vendor/spec-core/text/width.js.map +1 -0
  77. package/dist/versions.d.ts +27 -1
  78. package/dist/versions.js +23 -1
  79. package/dist/versions.js.map +1 -1
  80. package/dist/workspace.d.ts +6 -1
  81. package/dist/workspace.js +17 -6
  82. package/dist/workspace.js.map +1 -1
  83. package/package.json +1 -1
  84. package/skills/close-round/SKILL.md +4 -1
@@ -29,8 +29,11 @@
29
29
  * - Braces expand first, and each alternative reads as it would written on
30
30
  * its own: `{src/,lib}` is `src/` or `lib`, and a trailing `/` means the
31
31
  * directory's contents - in an exclusion, the directory and its contents.
32
- * One that names no path, `{./,lib}` or `{,lib}`, is refused, as `./` and
33
- * the empty pattern are.
32
+ * `{/docs,lib}` is `/docs` or `lib`, the leading `/` rooting or anchoring
33
+ * that alternative as it would the pattern. One that names no path,
34
+ * `{./,lib}` or `{,lib}`, is refused, as `./` and the empty pattern are.
35
+ * - A leading `./` is the current directory, and goes with the slashes after
36
+ * it, as POSIX reads them: `.//docs` is `docs`, and never `/docs`.
34
37
  * - Case is the caller's decision, stated every time. A result must not
35
38
  * depend on the host it ran on.
36
39
  * - A malformed pattern is an error, never a literal. An unclosed `[` or `{`,
@@ -133,3 +136,86 @@ export type GlobListParse = {
133
136
  * deciding the paths it matches, a leading `!` taking them back out.
134
137
  */
135
138
  export declare function parseGlobList(patterns: readonly string[], options: GlobOptions): GlobListParse;
139
+ /** One text a pattern's braces give, as every dialect reads its start. */
140
+ export interface GlobAlternative {
141
+ /**
142
+ * Led by a `/`, the pattern's or its own with no `./` of the pattern's
143
+ * before it: rooted at the filesystem's root in the `path` and `ripgrep`
144
+ * dialects, and anchored at the repository root in `gitignore`.
145
+ */
146
+ readonly rooted: boolean;
147
+ /**
148
+ * The text without the `./` and the slashes it starts with, the pattern's
149
+ * included, and with each `}` and `,` no group took written as a class of
150
+ * that one character, so that it reads the same inside braces again. Alone,
151
+ * it reads the same behind a `/` when rooted and behind a `./` when not,
152
+ * which keeps a `!` it starts with a character. A trailing `/` stays: what
153
+ * it means is the dialect's.
154
+ */
155
+ readonly text: string;
156
+ }
157
+ export type GlobAlternatives = {
158
+ readonly ok: true;
159
+ /** Led by a `/` of its own, before any `./`, which roots every alternative. */
160
+ readonly rooted: boolean;
161
+ /** In the order the braces give them. */
162
+ readonly alternatives: readonly GlobAlternative[];
163
+ } | {
164
+ readonly ok: false;
165
+ readonly error: string;
166
+ };
167
+ /**
168
+ * The alternatives a pattern's braces give, each read at its start as
169
+ * {@link parseGlob} reads it, for a tool that reads a pattern's alternatives
170
+ * one by one - to anchor a rooted one, to hand each to another engine - and
171
+ * would otherwise expand braces and read a leading `./` and `/` again itself.
172
+ *
173
+ * Braces expand first, and an alternative is rooted by a leading `/` on the
174
+ * pattern, or on the text the braces give when no `./` of the pattern's stands
175
+ * before it: `{/docs,x}` is `docs` rooted and `x`; `./{/docs,x}` and
176
+ * `{.//docs,x}` are `docs` and `x`, neither rooted; `a/{/b,c}` is `a//b` and
177
+ * `a/c`. A `!` after a `./` is a character, as it is to `parseGlob`: `./!a` is
178
+ * the name `!a`.
179
+ *
180
+ * A reading, not a verdict: refused are only what `parseGlob` refuses before
181
+ * it reads an alternative, in its words, and braces that do not expand. An
182
+ * alternative `parseGlob` refuses - `{/,x}` gives `/` - is given as read.
183
+ */
184
+ export declare function globAlternatives(source: string, options?: Pick<GlobOptions, 'backslash'>): GlobAlternatives;
185
+ export type GlobRebase = {
186
+ readonly ok: true;
187
+ readonly pattern: string;
188
+ } | {
189
+ readonly ok: false;
190
+ readonly error: string;
191
+ };
192
+ /**
193
+ * A list entry typed in a directory below the root, rewritten to read from
194
+ * the root as the `path` dialect reads it: for a tool run in a subdirectory
195
+ * that matches whole paths from the root, where what is typed means what it
196
+ * names from where it was typed.
197
+ *
198
+ * `directory` is where the entry was typed, a path under the root; at the
199
+ * root itself, `''`, there is nothing to rewrite. The entry is read as
200
+ * {@link parseGlobList} reads one: a leading `!` stays in front, and what
201
+ * follows is the pattern. Braces expand first, and each alternative is
202
+ * rebased as that text written alone would be:
203
+ *
204
+ * - one with a leading `/` is rooted at the filesystem's root, which no
205
+ * directory moves, and is kept: `/docs` is `/docs` typed anywhere;
206
+ * - any other gets the directory in front, a leading `..` climbing out of
207
+ * it: `docs/*.md` typed in `sub` is `sub/docs/*.md`, and `../*.md` typed in
208
+ * `docs/deep` is `docs/*.md`.
209
+ *
210
+ * When every alternative is rebased alike, the braces stay as written, `{a,b}`
211
+ * typed in `sub` being `sub/{a,b}`; when they are not, each is written out:
212
+ * `{/docs,x}` is `{/docs,sub/x}`.
213
+ *
214
+ * Refused, with the reason: what {@link parseGlob} refuses before it reads an
215
+ * alternative, in its words; braces that do not expand; a `..` that climbs
216
+ * above the root; a directory no pattern can name; and alternatives written
217
+ * out that hold a `,` or a `}`, which would read as braces. Anything else a
218
+ * rebased pattern holds is for `parseGlob` to refuse, as it would the same
219
+ * pattern typed at the root.
220
+ */
221
+ export declare function rebaseGlob(entry: string, directory: string, options?: Pick<GlobOptions, 'backslash'>): GlobRebase;
@@ -29,8 +29,11 @@
29
29
  * - Braces expand first, and each alternative reads as it would written on
30
30
  * its own: `{src/,lib}` is `src/` or `lib`, and a trailing `/` means the
31
31
  * directory's contents - in an exclusion, the directory and its contents.
32
- * One that names no path, `{./,lib}` or `{,lib}`, is refused, as `./` and
33
- * the empty pattern are.
32
+ * `{/docs,lib}` is `/docs` or `lib`, the leading `/` rooting or anchoring
33
+ * that alternative as it would the pattern. One that names no path,
34
+ * `{./,lib}` or `{,lib}`, is refused, as `./` and the empty pattern are.
35
+ * - A leading `./` is the current directory, and goes with the slashes after
36
+ * it, as POSIX reads them: `.//docs` is `docs`, and never `/docs`.
34
37
  * - Case is the caller's decision, stated every time. A result must not
35
38
  * depend on the host it ran on.
36
39
  * - A malformed pattern is an error, never a literal. An unclosed `[` or `{`,
@@ -74,34 +77,35 @@ export function compileGlob(source, options) {
74
77
  throw new GlobError(source, result);
75
78
  return result;
76
79
  }
77
- function build(source, options) {
78
- const escapes = options.backslash !== 'separator';
79
- let pattern = source.trim();
80
- if (!escapes)
81
- pattern = pattern.replace(/\\/g, '/');
82
- const written = pattern;
80
+ /**
81
+ * A pattern as every dialect takes it - trimmed, and each `\` a separator
82
+ * when the caller asks - or the reason it is refused before anything in it is
83
+ * read.
84
+ */
85
+ function prepare(source, escapes) {
86
+ const trimmed = source.trim();
87
+ const pattern = escapes ? trimmed : trimmed.replace(/\\/g, '/');
83
88
  if (pattern.length === 0)
84
89
  return 'the pattern is empty';
85
90
  if (pattern.startsWith('!'))
86
91
  return 'a negated pattern is a list entry, not a glob; narrow the positive pattern';
87
92
  if (/(?:^|[^\\])[?*+@!]\([^)]*\|/.test(pattern))
88
93
  return EXTGLOB;
89
- while (pattern.startsWith('./'))
90
- pattern = pattern.slice(2);
91
- // A leading slash roots a pattern at the filesystem's root in the path and
92
- // ripgrep dialects, and only anchors it at the repository root in gitignore.
93
- let rooted = false;
94
- let anchored = false;
95
- if (pattern.startsWith('/')) {
96
- if (options.dialect === 'gitignore')
97
- anchored = true;
98
- else
99
- rooted = true;
100
- pattern = pattern.replace(/^\/+/, '');
101
- }
102
- if (pattern.length === 0)
94
+ return { pattern };
95
+ }
96
+ function build(source, options) {
97
+ const escapes = options.backslash !== 'separator';
98
+ const prepared = prepare(source, escapes);
99
+ if (typeof prepared === 'string')
100
+ return prepared;
101
+ const { pattern } = prepared;
102
+ // The pattern's own leading slash is taken off before the braces expand, so
103
+ // that the root itself, and a text the braces give, are refused in the
104
+ // words they always were: `/{./,a}` is told `./`.
105
+ const whole = unrooted(pattern);
106
+ if (whole.text.length === 0)
103
107
  return 'the pattern names the root itself, not a path under it';
104
- const expanded = expandBraces(pattern, escapes);
108
+ const expanded = expandBraces(whole.text, escapes);
105
109
  if (typeof expanded === 'string')
106
110
  return expanded;
107
111
  const alternatives = [];
@@ -111,13 +115,20 @@ function build(source, options) {
111
115
  // Each text the braces give is shorter than the pattern, so only a
112
116
  // pattern without braces is its own text.
113
117
  if (namesNoPath(text))
114
- return text === pattern ? 'the pattern names no path' : bracesNamingNoPath(text);
115
- const parsed = parseAlternative(trailingSlash(text, options.dialect), escapes);
118
+ return text === whole.text ? 'the pattern names no path' : bracesNamingNoPath(text);
119
+ // Its leading slash is read as the pattern's is, since braces expand
120
+ // before anything else is decided: `{/docs,x}` is `/docs` or `x`. Left to
121
+ // `parseAlternative`, the slash would be an empty segment, dropped, and
122
+ // `/docs` would read as `docs`. A slash after a segment, as in
123
+ // `a/{/b,c}`, starts no text, and is the empty segment of `a//b`; one
124
+ // after the pattern's `./` roots nothing either.
125
+ const alone = unrooted(text);
126
+ const parsed = parseAlternative(trailingSlash(alone.text, options.dialect), escapes);
116
127
  if (parsed === GLOBSTAR_IN_NAME)
117
- return `${GLOBSTAR_IN_NAME}: ${globstarAdvice(written)}`;
128
+ return `${GLOBSTAR_IN_NAME}: ${globstarAdvice(pattern)}`;
118
129
  if (typeof parsed === 'string')
119
130
  return parsed;
120
- alternatives.push(parsed);
131
+ alternatives.push({ ...parsed, rooted: rootedIn(whole, alone) });
121
132
  }
122
133
  const builder = new Builder();
123
134
  const fragments = [];
@@ -125,7 +136,7 @@ function build(source, options) {
125
136
  let automaton;
126
137
  try {
127
138
  for (const alternative of alternatives) {
128
- const compiled = compileAlternative(builder, alternative, options, rooted, anchored);
139
+ const compiled = compileAlternative(builder, alternative, options);
129
140
  fragments.push(compiled.fragment);
130
141
  bases.push(compiled.base);
131
142
  }
@@ -261,6 +272,33 @@ function classEnd(pattern, open, escapes) {
261
272
  return -1;
262
273
  }
263
274
  /* ---------------------------------------------------------------- segments */
275
+ /**
276
+ * A pattern, or a text its braces give, without the `./` and the slashes it
277
+ * starts with; whether a slash led it, which roots it; and whether a `./`
278
+ * did, which leaves every slash after it rootless. What a leading slash means
279
+ * is the dialect's: it roots a pattern at the filesystem's root in `path` and
280
+ * `ripgrep`, and only anchors it at the repository root in `gitignore`, as
281
+ * git reads one.
282
+ *
283
+ * The slashes after a `./` go with it, as POSIX reads `.//docs` as `./docs`:
284
+ * the current directory, then `docs`. Left behind, they would lead what is
285
+ * left and root it, and `.//docs` would be `/docs`. A slash the braces give
286
+ * after a pattern's `./` is one of them, so `./{/docs,x}`, which is
287
+ * `.//docs` or `./x`, roots neither.
288
+ */
289
+ function unrooted(text) {
290
+ let rest = text;
291
+ while (rest.startsWith('./'))
292
+ rest = rest.slice(2).replace(/^\/+/, '');
293
+ return { text: rest.replace(/^\/+/, ''), rooted: rest.startsWith('/'), dotted: rest !== text };
294
+ }
295
+ /**
296
+ * Whether a text the braces give is rooted: by the pattern's own leading
297
+ * slash, or by its own when the pattern's `./` does not stand before it.
298
+ */
299
+ function rootedIn(whole, alone) {
300
+ return whole.rooted || (alone.rooted && !whole.dotted);
301
+ }
264
302
  /** Whether a pattern's text names no path: nothing in it but `/` and `.` segments. */
265
303
  function namesNoPath(text) {
266
304
  return text.split('/').every((part) => part === '' || part === '.');
@@ -302,7 +340,7 @@ function parseAlternative(text, escapes) {
302
340
  if (part === '' || part === '.')
303
341
  continue;
304
342
  if (part === '..')
305
- return 'a pattern cannot climb out of its root with ".."';
343
+ return CLIMBS_OUT;
306
344
  if (part === '**') {
307
345
  if (segments[segments.length - 1]?.kind !== 'globstar')
308
346
  segments.push({ kind: 'globstar' });
@@ -315,6 +353,7 @@ function parseAlternative(text, escapes) {
315
353
  }
316
354
  return { segments, slashed: raw.filter((part) => part !== '' && part !== '.').length > 1 };
317
355
  }
356
+ const CLIMBS_OUT = 'a pattern cannot climb out of its root with ".."';
318
357
  const EXTGLOB = 'extended globs such as "+(a|b)" are not supported: write alternatives as "{a,b}", and a literal parenthesis as "[(]"';
319
358
  const GLOBSTAR_IN_NAME = '"**" means any number of directories only as a whole segment';
320
359
  /**
@@ -504,14 +543,14 @@ function segmentsFragment(b, segments) {
504
543
  });
505
544
  return b.sequence(parts);
506
545
  }
507
- function compileAlternative(b, alternative, options, rooted, anchored) {
508
- const { segments } = alternative;
546
+ function compileAlternative(b, alternative, options) {
547
+ const { segments, rooted } = alternative;
509
548
  const body = segmentsFragment(b, segments);
510
549
  const root = rooted ? '/' : '';
511
550
  const literal = literalText(segments);
512
551
  const leading = leadingLiteral(segments);
513
552
  if (options.dialect === 'gitignore') {
514
- const floating = !anchored && !alternative.slashed;
553
+ const floating = !rooted && !alternative.slashed;
515
554
  const parts = [body, b.optional(beneath(b))];
516
555
  if (floating)
517
556
  parts.unshift(anyDirectories(b));
@@ -597,4 +636,167 @@ export function parseGlobList(patterns, options) {
597
636
  },
598
637
  };
599
638
  }
639
+ /**
640
+ * The alternatives a pattern's braces give, each read at its start as
641
+ * {@link parseGlob} reads it, for a tool that reads a pattern's alternatives
642
+ * one by one - to anchor a rooted one, to hand each to another engine - and
643
+ * would otherwise expand braces and read a leading `./` and `/` again itself.
644
+ *
645
+ * Braces expand first, and an alternative is rooted by a leading `/` on the
646
+ * pattern, or on the text the braces give when no `./` of the pattern's stands
647
+ * before it: `{/docs,x}` is `docs` rooted and `x`; `./{/docs,x}` and
648
+ * `{.//docs,x}` are `docs` and `x`, neither rooted; `a/{/b,c}` is `a//b` and
649
+ * `a/c`. A `!` after a `./` is a character, as it is to `parseGlob`: `./!a` is
650
+ * the name `!a`.
651
+ *
652
+ * A reading, not a verdict: refused are only what `parseGlob` refuses before
653
+ * it reads an alternative, in its words, and braces that do not expand. An
654
+ * alternative `parseGlob` refuses - `{/,x}` gives `/` - is given as read.
655
+ */
656
+ export function globAlternatives(source, options = {}) {
657
+ const escapes = options.backslash !== 'separator';
658
+ const prepared = prepare(source, escapes);
659
+ if (typeof prepared === 'string')
660
+ return { ok: false, error: prepared };
661
+ const whole = unrooted(prepared.pattern);
662
+ const texts = expandBraces(whole.text, escapes);
663
+ if (typeof texts === 'string')
664
+ return { ok: false, error: texts };
665
+ return {
666
+ ok: true,
667
+ rooted: whole.rooted,
668
+ alternatives: texts.map((text) => {
669
+ const alone = unrooted(text);
670
+ // Past its root, a text may start with a `./` of its own, as `/./docs`
671
+ // does, and names the same without it.
672
+ return { rooted: rootedIn(whole, alone), text: asCharacters(unrooted(alone.text).text) };
673
+ }),
674
+ };
675
+ }
676
+ /**
677
+ * A text the braces gave, with each `}` and `,` no group took written as the
678
+ * class of that one character: once the braces have expanded, that is every
679
+ * one outside a class and not escaped. Escapes and classes are copied as
680
+ * written. A `\` is always an escape here, since one read as a separator was
681
+ * a `/` before the braces expanded. A step past the end reads `''`, which
682
+ * adds nothing, so the bound could be one further.
683
+ */
684
+ function asCharacters(text) {
685
+ let out = '';
686
+ for (let i = 0; i < text.length; i += 1) {
687
+ const ch = text.charAt(i);
688
+ let next = i + 1;
689
+ if (ch === '\\')
690
+ next = i + 2;
691
+ else if (ch === '[')
692
+ next = Math.max(next, classEnd(text, i, true) + 1);
693
+ out += ch === '}' || ch === ',' ? `[${ch}]` : text.slice(i, next);
694
+ i = next - 1;
695
+ }
696
+ return out;
697
+ }
698
+ /**
699
+ * A list entry typed in a directory below the root, rewritten to read from
700
+ * the root as the `path` dialect reads it: for a tool run in a subdirectory
701
+ * that matches whole paths from the root, where what is typed means what it
702
+ * names from where it was typed.
703
+ *
704
+ * `directory` is where the entry was typed, a path under the root; at the
705
+ * root itself, `''`, there is nothing to rewrite. The entry is read as
706
+ * {@link parseGlobList} reads one: a leading `!` stays in front, and what
707
+ * follows is the pattern. Braces expand first, and each alternative is
708
+ * rebased as that text written alone would be:
709
+ *
710
+ * - one with a leading `/` is rooted at the filesystem's root, which no
711
+ * directory moves, and is kept: `/docs` is `/docs` typed anywhere;
712
+ * - any other gets the directory in front, a leading `..` climbing out of
713
+ * it: `docs/*.md` typed in `sub` is `sub/docs/*.md`, and `../*.md` typed in
714
+ * `docs/deep` is `docs/*.md`.
715
+ *
716
+ * When every alternative is rebased alike, the braces stay as written, `{a,b}`
717
+ * typed in `sub` being `sub/{a,b}`; when they are not, each is written out:
718
+ * `{/docs,x}` is `{/docs,sub/x}`.
719
+ *
720
+ * Refused, with the reason: what {@link parseGlob} refuses before it reads an
721
+ * alternative, in its words; braces that do not expand; a `..` that climbs
722
+ * above the root; a directory no pattern can name; and alternatives written
723
+ * out that hold a `,` or a `}`, which would read as braces. Anything else a
724
+ * rebased pattern holds is for `parseGlob` to refuse, as it would the same
725
+ * pattern typed at the root.
726
+ */
727
+ export function rebaseGlob(entry, directory, options = {}) {
728
+ if (directory === '')
729
+ return { ok: true, pattern: entry };
730
+ const directories = directory.split('/');
731
+ if (directories.some((name) => name === '' || name === '.' || name === '..')) {
732
+ throw new Error(`"${directory}" is not a directory under the root`);
733
+ }
734
+ const escapes = options.backslash !== 'separator';
735
+ const trimmed = entry.trim();
736
+ const negation = trimmed.startsWith('!') ? '!' : '';
737
+ const prepared = prepare(trimmed.slice(negation.length), escapes);
738
+ if (typeof prepared === 'string')
739
+ return { ok: false, error: prepared };
740
+ const { pattern } = prepared;
741
+ const whole = unrooted(pattern);
742
+ if (whole.rooted)
743
+ return { ok: true, pattern: negation + pattern };
744
+ const texts = expandBraces(whole.text, escapes);
745
+ if (typeof texts === 'string')
746
+ return { ok: false, error: texts };
747
+ const pieces = [];
748
+ const depths = new Set();
749
+ let rooted = false;
750
+ for (const text of texts) {
751
+ // Rooted as `build` roots it, the pattern's own slash having been read.
752
+ if (rootedIn(whole, unrooted(text))) {
753
+ rooted = true;
754
+ pieces.push(text);
755
+ continue;
756
+ }
757
+ const moved = climb(text, directories);
758
+ if (moved === null)
759
+ return { ok: false, error: CLIMBS_OUT };
760
+ depths.add(moved.directories.length);
761
+ pieces.push(spell(moved));
762
+ }
763
+ if (depths.size === 0)
764
+ return { ok: true, pattern: negation + pattern };
765
+ if (/^[!\s]|[*?[{\\]/.test(directory)) {
766
+ return {
767
+ ok: false,
768
+ error: `the directory "${directory}" cannot be named in a pattern: a "*", "?", "[", "{" or "\\" in it, or a "!" or a space it starts with, would be read as syntax`,
769
+ };
770
+ }
771
+ // Every text the braces give starts with the pattern's own leading `..`
772
+ // segments, and one of them has just climbed without leaving the root.
773
+ const lead = climb(whole.text, directories);
774
+ if (!rooted && depths.size === 1 && depths.has(lead.directories.length))
775
+ return { ok: true, pattern: negation + spell(lead) };
776
+ const split = pieces.find((piece) => /[,}]/.test(piece));
777
+ if (split !== undefined) {
778
+ return { ok: false, error: `the braces cannot be rebased one alternative at a time: "${split}" holds a "," or a "}"` };
779
+ }
780
+ return { ok: true, pattern: `${negation}{${pieces.join(',')}}` };
781
+ }
782
+ /** A relative text's leading `..` segments taken off the directories it was typed in, or `null` past the root. */
783
+ function climb(text, directories) {
784
+ let kept = directories.length;
785
+ let rest = unrooted(text).text;
786
+ while (rest === '..' || rest.startsWith('../')) {
787
+ if (kept === 0)
788
+ return null;
789
+ kept -= 1;
790
+ rest = unrooted(rest.slice(3)).text;
791
+ }
792
+ return { directories: directories.slice(0, kept), rest };
793
+ }
794
+ /**
795
+ * A rebased relative text. At the root itself it is read from there behind a
796
+ * `./`, which keeps a slash the braces give after it from rooting it, and
797
+ * leaves the root refused as `./` is.
798
+ */
799
+ function spell(moved) {
800
+ return moved.directories.length > 0 ? `${moved.directories.join('/')}/${moved.rest}` : `./${moved.rest}`;
801
+ }
600
802
  //# sourceMappingURL=glob.js.map