@suss/packs 0.34.0 → 0.35.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.
package/dist/rails.js CHANGED
@@ -1,22 +1,26 @@
1
1
  // ../framework/rails/dist/index.js
2
- import fs4 from "fs";
3
- import path4 from "path";
4
- import { z } from "zod";
2
+ import fs5 from "fs";
3
+ import path5 from "path";
4
+ import { z as z15 } from "zod";
5
5
  import { underscoreConstantPath as underscoreConstantPath2 } from "@suss/adapter-ruby";
6
6
  import fs from "fs";
7
7
  import path from "path";
8
+ import fs2 from "fs";
9
+ import path2 from "path";
8
10
  import {
9
11
  bodyStatements,
12
+ children,
10
13
  field,
11
14
  parseRubySync,
12
15
  underscoreConstantPath
13
16
  } from "@suss/adapter-ruby";
14
- import fs2 from "fs";
15
- import path2 from "path";
16
17
  import fs3 from "fs";
17
18
  import path3 from "path";
19
+ import fs4 from "fs";
20
+ import path4 from "path";
18
21
  import {
19
22
  bodyStatements as bodyStatements2,
23
+ children as children2,
20
24
  field as field2,
21
25
  hashKeySymbolName,
22
26
  nestedStatements,
@@ -24,6 +28,1448 @@ import {
24
28
  stringValueOf,
25
29
  symbolValue
26
30
  } from "@suss/adapter-ruby";
31
+
32
+ // ../ir-core/dist/chunk-A46EUIKC.js
33
+ import { z as z13 } from "zod";
34
+ import { z as z12 } from "zod";
35
+ import { z } from "zod";
36
+ import { z as z2 } from "zod";
37
+ import { z as z3 } from "zod";
38
+ import { z as z4 } from "zod";
39
+ import { z as z5 } from "zod";
40
+ import { z as z6 } from "zod";
41
+ import { z as z7 } from "zod";
42
+ import { z as z9 } from "zod";
43
+ import { z as z8 } from "zod";
44
+ import { z as z10 } from "zod";
45
+ import { z as z11 } from "zod";
46
+ function gqlIdentityKey(typeName, fieldName) {
47
+ return `gql:${typeName}.${fieldName}`;
48
+ }
49
+ function fnIdentityKey(packageName, exportPath) {
50
+ return `fn:${packageName}::${exportPath.join(".")}`;
51
+ }
52
+ function busIdentityKey(messageBus, subject) {
53
+ return `bus:${messageBus} ${subject}`;
54
+ }
55
+ function unitIdentityKey(deploymentTarget, instanceName) {
56
+ return `unit:${deploymentTarget} ${instanceName}`;
57
+ }
58
+ function metricIdentityKey(metricSystem, metricType) {
59
+ return `metric:${metricSystem} ${metricType}`;
60
+ }
61
+ function ioIdentityKey(target) {
62
+ return `io:${target}`;
63
+ }
64
+ function defineBoundarySemantics(definition) {
65
+ return definition;
66
+ }
67
+ var FunctionCallSemanticsSchema = z.object({
68
+ name: z.literal("function-call"),
69
+ /**
70
+ * Where the function lives inside this repository. It is either the
71
+ * name of a module the project lists in `suss.json`, such as
72
+ * `"billing"`, or a module path, such as the file a server action is
73
+ * written in. A name has no separator in it and a path always does.
74
+ * Packs that don't do cross-module pairing can leave it unset.
75
+ */
76
+ module: z.string().optional(),
77
+ /** Named export within the module, when applicable. */
78
+ exportName: z.string().optional(),
79
+ /**
80
+ * Package name, as written in `package.json`, when this identity
81
+ * refers to a public package export, for example
82
+ * `"@suss/behavioral-ir"`. Packs that resolve a package's public
83
+ * surface (the `packageExports` discovery variant) set it alongside
84
+ * `exportPath`. It is different from `module`, which is a
85
+ * repo-relative module path used for pairing inside one repo.
86
+ */
87
+ package: z.string().optional(),
88
+ /**
89
+ * Path to the exported binding within the package, starting with the
90
+ * sub-path key when one is used. Examples:
91
+ * `["parseSummary"]`: a root export
92
+ * `["schemas", "BoundaryBindingSchema"]`: the sub-path `./schemas`
93
+ *
94
+ * The first segment is the sub-path without the leading `./`, and
95
+ * `"."` is left out. The last segment is the exported name.
96
+ */
97
+ exportPath: z.array(z.string()).optional()
98
+ });
99
+ function isModuleName(module) {
100
+ return !module.includes("/") && !module.includes("\\");
101
+ }
102
+ var functionCallSemantics = defineBoundarySemantics({
103
+ name: "function-call",
104
+ schema: FunctionCallSemanticsSchema,
105
+ // A call inside the process does not produce a span, and the
106
+ // conventions have no attributes for it.
107
+ semconv: {},
108
+ behavior: {
109
+ /** A call returns a value, with no status. */
110
+ exchangesHttpResponses: false,
111
+ leavesTheProcess: false,
112
+ reportsUnpairedItself: false,
113
+ /**
114
+ * In-repo units such as components have no key on purpose. They
115
+ * pair through call edges instead of the keyed pass, so a missing
116
+ * key does not make them unreachable.
117
+ */
118
+ canPair: () => true,
119
+ rewritePaths(semantics, rewrite) {
120
+ if (semantics.module === void 0 || isModuleName(semantics.module)) {
121
+ return semantics;
122
+ }
123
+ return { ...semantics, module: rewrite(semantics.module) };
124
+ },
125
+ /**
126
+ * `"fn:<package>::<exportPath>"` when both are set, and
127
+ * `"fn:<module>::<exportName>"` for an in-repo unit that gives its
128
+ * module, as a server action does. Components and plain handlers
129
+ * give neither and have no key.
130
+ */
131
+ identityKey(semantics) {
132
+ if (semantics.package !== void 0 && semantics.exportPath !== void 0 && semantics.exportPath.length > 0) {
133
+ return fnIdentityKey(semantics.package, semantics.exportPath);
134
+ }
135
+ if (semantics.module !== void 0 && semantics.exportName !== void 0) {
136
+ return fnIdentityKey(semantics.module, [semantics.exportName]);
137
+ }
138
+ return null;
139
+ }
140
+ }
141
+ });
142
+ var GraphqlOperationSemanticsSchema = z2.object({
143
+ name: z2.literal("graphql-operation"),
144
+ /** Optional operation name. An anonymous query or mutation leaves it unset. */
145
+ operationName: z2.string().optional(),
146
+ operationType: z2.enum(["query", "mutation", "subscription"])
147
+ });
148
+ var graphqlOperationSemantics = defineBoundarySemantics({
149
+ name: "graphql-operation",
150
+ schema: GraphqlOperationSemanticsSchema,
151
+ semconv: {
152
+ operationName: { name: "graphql.operation.name" },
153
+ operationType: { name: "graphql.operation.type" }
154
+ },
155
+ behavior: {
156
+ /**
157
+ * An operation gets a data-and-errors document back, which the
158
+ * GraphQL contract checker reads. Status codes do not apply.
159
+ */
160
+ exchangesHttpResponses: false,
161
+ leavesTheProcess: true,
162
+ reportsUnpairedItself: false,
163
+ identityKey: () => null,
164
+ /** No key on purpose. The dedicated pass pairs an operation by its document. */
165
+ canPair: () => true,
166
+ /** `query GetUser`. Only for display, since the contract checker does the pairing. */
167
+ displayLabel(semantics) {
168
+ return `${semantics.operationType} ${semantics.operationName ?? "<anonymous>"}`;
169
+ }
170
+ }
171
+ });
172
+ var GraphqlResolverSemanticsSchema = z3.object({
173
+ name: z3.literal("graphql-resolver"),
174
+ /**
175
+ * GraphQL type the resolver attaches to: "Query", "Mutation",
176
+ * "Subscription", or an object-type name. Null when the source never
177
+ * says which one, as with a `@Resolver()` class that has no argument.
178
+ */
179
+ typeName: z3.string().min(1).nullable(),
180
+ /** Field name on that type. */
181
+ fieldName: z3.string()
182
+ });
183
+ var graphqlResolverSemantics = defineBoundarySemantics({
184
+ name: "graphql-resolver",
185
+ schema: GraphqlResolverSemanticsSchema,
186
+ // The conventions describe the operation a client sent. They have no
187
+ // attribute for the resolver the server ran for one field of it.
188
+ semconv: {},
189
+ behavior: {
190
+ /** A resolver returns a field value, with no status. */
191
+ exchangesHttpResponses: false,
192
+ // A resolver runs because a client outside the process sent a query,
193
+ // even though the server calls it in its own process.
194
+ leavesTheProcess: true,
195
+ reportsUnpairedItself: false,
196
+ /**
197
+ * `"gql:<TypeName>.<fieldName>"`, or null when the type is null or
198
+ * the field is empty, which is how a resolver reports that the
199
+ * source never gave them.
200
+ */
201
+ identityKey(semantics) {
202
+ if (semantics.typeName === null || semantics.fieldName === "") {
203
+ return null;
204
+ }
205
+ return gqlIdentityKey(semantics.typeName, semantics.fieldName);
206
+ }
207
+ }
208
+ });
209
+ var IoSemanticsSchema = z4.object({
210
+ name: z4.literal("io"),
211
+ /** The stream, such as `stdout` or `stderr`. Null when the source does not say which. */
212
+ target: z4.string().nullable()
213
+ });
214
+ var ioSemantics = defineBoundarySemantics({
215
+ name: "io",
216
+ schema: IoSemanticsSchema,
217
+ // The semantic conventions have no attribute for a process's own streams.
218
+ semconv: {},
219
+ behavior: {
220
+ exchangesHttpResponses: false,
221
+ leavesTheProcess: true,
222
+ reportsUnpairedItself: false,
223
+ identityKey(semantics) {
224
+ return semantics.target === null ? null : ioIdentityKey(semantics.target);
225
+ },
226
+ // Nothing declares the reading side, so a write has nothing to meet.
227
+ canPair: () => false
228
+ }
229
+ });
230
+ function parseChannel(channel) {
231
+ const hash = channel.indexOf("#");
232
+ if (hash === -1) {
233
+ return { bus: null, subject: channel };
234
+ }
235
+ return { bus: channel.slice(0, hash), subject: channel.slice(hash + 1) };
236
+ }
237
+ function busesAgree(a, b) {
238
+ return a === null || b === null || a === b;
239
+ }
240
+ function channelsPair(a, b) {
241
+ const left = parseChannel(a);
242
+ const right = parseChannel(b);
243
+ return left.subject === right.subject && busesAgree(left.bus, right.bus);
244
+ }
245
+ var MessageBusSemanticsSchema = z5.object({
246
+ name: z5.literal("message-bus"),
247
+ messageBus: z5.enum([
248
+ // The conventions write these two AWS buses differently, one with an
249
+ // underscore and one with a dot. Both are as the conventions give them.
250
+ "aws_sqs",
251
+ "aws.sns",
252
+ "s3",
253
+ "eventbridge",
254
+ // A Kinesis stream and a Firehose delivery stream are different
255
+ // APIs with different destinations, so a stream named `orders` and
256
+ // a delivery stream named `orders` are not the same channel.
257
+ "aws_kinesis",
258
+ "aws_firehose",
259
+ "gcp_pubsub",
260
+ "bullmq",
261
+ "kafka",
262
+ "nats",
263
+ "cloudflare-queues",
264
+ "cloudflare-cron",
265
+ "cloudflare-tail"
266
+ ]),
267
+ /**
268
+ * A stable channel identifier: a CloudFormation logical id, a queue or
269
+ * topic name, a subject pattern, or `bus#detailType`. Null when this
270
+ * source does not say which channel, as with a send whose queue the
271
+ * code picks at run time, or a receive whose queue is set in the
272
+ * event-source mapping.
273
+ */
274
+ channel: z5.string().min(1).nullable()
275
+ });
276
+ var messageBusSemantics = defineBoundarySemantics({
277
+ name: "message-bus",
278
+ schema: MessageBusSemanticsSchema,
279
+ semconv: {
280
+ messageBus: { name: "messaging.system" },
281
+ channel: { name: "messaging.destination.name" }
282
+ },
283
+ behavior: {
284
+ /** A message goes onto the channel and nothing comes back. */
285
+ exchangesHttpResponses: false,
286
+ leavesTheProcess: true,
287
+ /**
288
+ * `checkMessageBus` reports every channel, unused ones included, so
289
+ * the generic unmatched list leaves them out.
290
+ */
291
+ reportsUnpairedItself: true,
292
+ /**
293
+ * A channel that is null here can still pair. The dedicated pass
294
+ * follows environment variable chains before comparing, so a
295
+ * consumer whose channel is set at run time meets the template that
296
+ * declares it.
297
+ */
298
+ canPair: () => true,
299
+ /**
300
+ * `"bus:<messageBus> <subject>"`, or null when the channel is null
301
+ * or its subject is empty.
302
+ *
303
+ * The key has the subject and leaves out the bus, so a template
304
+ * writing `default#order.placed` and a handler writing
305
+ * `order.placed` land in one bucket, where `sidesAgree` compares the
306
+ * buses. Keying on the bus would separate a side that cannot know
307
+ * its bus from the side that can.
308
+ *
309
+ * The bus technology stays in, because a queue and an event router
310
+ * are different destinations even with the same subject. Case stays
311
+ * too, because AWS compares detail-types and queue ids byte for byte.
312
+ */
313
+ identityKey(semantics) {
314
+ if (semantics.channel === null) {
315
+ return null;
316
+ }
317
+ const { subject } = parseChannel(semantics.channel);
318
+ if (subject === "") {
319
+ return null;
320
+ }
321
+ return busIdentityKey(semantics.messageBus, subject);
322
+ },
323
+ sidesAgree(a, b) {
324
+ if (a.channel === null || b.channel === null) {
325
+ return false;
326
+ }
327
+ return channelsPair(a.channel, b.channel);
328
+ },
329
+ /**
330
+ * The whole channel, bus included, where the identity key has only
331
+ * the subject. A reader looking at unmatched channels needs to see
332
+ * which bus each one was on.
333
+ */
334
+ displayLabel(semantics) {
335
+ if (semantics.channel === null) {
336
+ return `bus:${semantics.messageBus} (channel named at runtime)`;
337
+ }
338
+ return `bus:${semantics.messageBus} ${semantics.channel}`;
339
+ }
340
+ }
341
+ });
342
+ var MetricSemanticsSchema = z6.object({
343
+ name: z6.literal("metric"),
344
+ /** The system the series is stored in, such as `cloud-monitoring` or `cloudwatch`. */
345
+ metricSystem: z6.string(),
346
+ /**
347
+ * The type string the system uses for the metric, written the same
348
+ * way on both sides. Null when the reader could not work the string
349
+ * out, as with a query it could not parse. A null type pairs with
350
+ * nothing, so it cannot pair with an unrelated metric that happens to
351
+ * share its source text.
352
+ */
353
+ metricType: z6.string().nullable()
354
+ });
355
+ var metricSemantics = defineBoundarySemantics({
356
+ name: "metric",
357
+ schema: MetricSemanticsSchema,
358
+ // OpenTelemetry identifies a metric by its instrument name. It has no
359
+ // attribute for the metric's system or its type string.
360
+ semconv: {},
361
+ behavior: {
362
+ exchangesHttpResponses: false,
363
+ leavesTheProcess: true,
364
+ reportsUnpairedItself: false,
365
+ identityKey(semantics) {
366
+ if (semantics.metricType === null) {
367
+ return null;
368
+ }
369
+ return metricIdentityKey(semantics.metricSystem, semantics.metricType);
370
+ }
371
+ }
372
+ });
373
+ var HOLE_SPLIT = /(\{[^}]*\})/;
374
+ function isHole(piece) {
375
+ return piece.startsWith("{") && piece.endsWith("}");
376
+ }
377
+ function parseBoundaryName(name) {
378
+ const parts = name.split(HOLE_SPLIT).filter((piece) => piece !== "").map(
379
+ (piece) => isHole(piece) ? { type: "hole", label: piece.slice(1, -1) } : { type: "text", text: piece }
380
+ );
381
+ const only = parts.length === 1 ? parts[0] : void 0;
382
+ if (only !== void 0 && only.type === "hole") {
383
+ return { type: "reference", path: only.label.split(".") };
384
+ }
385
+ if (parts.some((part) => part.type === "hole")) {
386
+ return { type: "pattern", parts };
387
+ }
388
+ return { type: "literal", value: name };
389
+ }
390
+ function patternHole(label) {
391
+ return `{${label}}`;
392
+ }
393
+ function namesNothing(name) {
394
+ return parseBoundaryName(name).type === "reference";
395
+ }
396
+ function referenceOf(name) {
397
+ if (name.type !== "reference") {
398
+ return null;
399
+ }
400
+ const root = name.path[0];
401
+ if (root === void 0 || name.path.some((part) => part === "")) {
402
+ return null;
403
+ }
404
+ return { root, fields: name.path.slice(1) };
405
+ }
406
+ function referenceFromName(name) {
407
+ return referenceOf(parseBoundaryName(name));
408
+ }
409
+ var SCHEME_ORIGIN = /^[a-zA-Z][a-zA-Z\d+.-]*:\/\/[^/]*/;
410
+ var PROTOCOL_RELATIVE_ORIGIN = /^\/\/[^/]*/;
411
+ function pathAfterOrigin(text) {
412
+ const withoutOrigin = text.replace(SCHEME_ORIGIN, "").replace(PROTOCOL_RELATIVE_ORIGIN, "");
413
+ const cut = withoutOrigin.search(/[?#]/);
414
+ return cut === -1 ? withoutOrigin : withoutOrigin.slice(0, cut);
415
+ }
416
+ var PRIVATE_SUFFIXES = [
417
+ "local",
418
+ "internal",
419
+ "localhost",
420
+ "lan",
421
+ "home.arpa",
422
+ "svc",
423
+ "test",
424
+ "example",
425
+ "invalid",
426
+ "example.com",
427
+ "example.net",
428
+ "example.org"
429
+ ];
430
+ var PRIVATE_IPV4 = /^(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.|0\.0\.0\.0$)/;
431
+ function hostnameOf(host) {
432
+ const lower = host.toLowerCase();
433
+ const name = lower.startsWith("[") ? lower.slice(0, lower.indexOf("]") + 1) : lower.split(":")[0] ?? lower;
434
+ return name.replace(/\.$/, "");
435
+ }
436
+ function hostCanBeOwn(host) {
437
+ const name = hostnameOf(host);
438
+ if (name === "[::1]" || /^\[f[cd]/.test(name) || PRIVATE_IPV4.test(name)) {
439
+ return true;
440
+ }
441
+ if (!name.includes(".")) {
442
+ return true;
443
+ }
444
+ return PRIVATE_SUFFIXES.some(
445
+ (suffix) => name === suffix || name.endsWith(`.${suffix}`)
446
+ );
447
+ }
448
+ var UNSPELLABLE_OPTION = /[()|?#{}]/;
449
+ function setPiece(options) {
450
+ if (options.some((option) => UNSPELLABLE_OPTION.test(option))) {
451
+ return null;
452
+ }
453
+ return `(${options.join("|")})`;
454
+ }
455
+ var ONE = { kind: "one" };
456
+ var OPTIONAL = { kind: "optional" };
457
+ var STAR = { kind: "star" };
458
+ var HOLE_SEGMENT = /^\{[^{}]*?([?+*]?)\}$/;
459
+ var SET_PIECE = /\(([^()]*)\)/;
460
+ var ALTERNATIVE_CAP = 64;
461
+ function alternativesOf(path6) {
462
+ let alternatives = [path6];
463
+ for (; ; ) {
464
+ const next = [];
465
+ let expanded = false;
466
+ for (const alternative of alternatives) {
467
+ const set = SET_PIECE.exec(alternative);
468
+ if (set === null || set.index === void 0) {
469
+ next.push(alternative);
470
+ continue;
471
+ }
472
+ expanded = true;
473
+ const head = alternative.slice(0, set.index);
474
+ const tail = alternative.slice(set.index + set[0].length);
475
+ for (const option of (set[1] ?? "").split("|")) {
476
+ next.push(head + option + tail);
477
+ }
478
+ }
479
+ if (!expanded) {
480
+ return alternatives;
481
+ }
482
+ if (next.length > ALTERNATIVE_CAP) {
483
+ return [path6.replace(new RegExp(SET_PIECE, "g"), "{value}")];
484
+ }
485
+ alternatives = next;
486
+ }
487
+ }
488
+ var HOLE_RANGE_ITEMS = {
489
+ "": [ONE],
490
+ "?": [OPTIONAL],
491
+ "+": [ONE, STAR],
492
+ "*": [STAR]
493
+ };
494
+ function escapedForRegex(text) {
495
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
496
+ }
497
+ function shapedItem(segment) {
498
+ const source = segment.split(/(\{[^}]*\}|\*)/g).map((part) => {
499
+ if (part === "*") {
500
+ return ".*";
501
+ }
502
+ if (part.startsWith("{")) {
503
+ return /[?*]\}$/.test(part) ? "[^/]*" : "[^/]+";
504
+ }
505
+ return escapedForRegex(part);
506
+ }).join("");
507
+ const holes = segment.match(/\{[^}]*\}|\*/g) ?? [];
508
+ const text = segment.replace(/\{[^}]*\}|\*/g, "");
509
+ return {
510
+ kind: "shaped",
511
+ shape: segment.replace(/\{[^}]*\}/g, "{}"),
512
+ test: new RegExp(`^${source}$`),
513
+ bare: holes.every((hole) => hole === "*" || /[?*]\}$/.test(hole)) ? text : null,
514
+ textLength: text.length
515
+ };
516
+ }
517
+ function itemsOf(segment) {
518
+ if (segment === "*") {
519
+ return [STAR];
520
+ }
521
+ const hole = HOLE_SEGMENT.exec(segment);
522
+ if (hole !== null) {
523
+ return HOLE_RANGE_ITEMS[hole[1] ?? ""] ?? [ONE];
524
+ }
525
+ if (segment.includes("{") || segment.includes("*")) {
526
+ return [shapedItem(segment)];
527
+ }
528
+ return [{ kind: "text", text: segment }];
529
+ }
530
+ function segmentsOf(alternative) {
531
+ const trimmed = alternative.startsWith("/") ? alternative.slice(1) : alternative;
532
+ return trimmed === "" ? [] : trimmed.split("/");
533
+ }
534
+ function patternSegmentsOf(alternative) {
535
+ const segments = segmentsOf(alternative);
536
+ const first = segments[0];
537
+ const opensWithBaseUrl = !alternative.startsWith("/") && first !== void 0 && HOLE_SEGMENT.test(first);
538
+ return opensWithBaseUrl ? segments.slice(1) : segments;
539
+ }
540
+ function pathAfterBaseUrl(path6) {
541
+ const segments = segmentsOf(path6);
542
+ const first = segments[0];
543
+ if (path6.startsWith("/") || first === void 0 || segments.length < 2 || !HOLE_SEGMENT.test(first)) {
544
+ return path6;
545
+ }
546
+ return `/${segments.slice(1).join("/")}`;
547
+ }
548
+ var PATTERNS = /* @__PURE__ */ new Map();
549
+ var PATTERN_CACHE_LIMIT = 5e4;
550
+ function patternOf(path6) {
551
+ const known = PATTERNS.get(path6);
552
+ if (known !== void 0) {
553
+ return known;
554
+ }
555
+ const pattern = alternativesOf(path6).map(
556
+ (alternative) => patternSegmentsOf(alternative).flatMap(itemsOf)
557
+ );
558
+ if (PATTERNS.size >= PATTERN_CACHE_LIMIT) {
559
+ PATTERNS.clear();
560
+ }
561
+ PATTERNS.set(path6, pattern);
562
+ return pattern;
563
+ }
564
+ function literalItemsOf(path6) {
565
+ return segmentsOf(path6).map((text) => ({ kind: "text", text }));
566
+ }
567
+ function absorbsAnything(item) {
568
+ return item.kind === "one" || item.kind === "optional" || item.kind === "star";
569
+ }
570
+ function meetOnOneSegment(a, b) {
571
+ if (absorbsAnything(a) || absorbsAnything(b)) {
572
+ return true;
573
+ }
574
+ if (a.kind === "text" && b.kind === "text") {
575
+ return a.text === b.text;
576
+ }
577
+ if (a.kind === "text" && b.kind === "shaped") {
578
+ return b.test.test(a.text);
579
+ }
580
+ if (a.kind === "shaped" && b.kind === "text") {
581
+ return a.test.test(b.text);
582
+ }
583
+ return a.kind === "shaped" && b.kind === "shaped" && a.shape === b.shape;
584
+ }
585
+ function skippable(item) {
586
+ return item.kind === "optional" || item.kind === "star";
587
+ }
588
+ function itemsMeet(a, b) {
589
+ const width = b.length + 1;
590
+ const seen = new Uint8Array((a.length + 1) * width);
591
+ const queue = [0];
592
+ seen[0] = 1;
593
+ const visit = (i, j) => {
594
+ const at = i * width + j;
595
+ if (seen[at] === 0) {
596
+ seen[at] = 1;
597
+ queue.push(at);
598
+ }
599
+ };
600
+ while (queue.length > 0) {
601
+ const at = queue.pop();
602
+ const i = Math.floor(at / width);
603
+ const j = at % width;
604
+ if (i === a.length && j === b.length) {
605
+ return true;
606
+ }
607
+ const left = a[i];
608
+ const right = b[j];
609
+ if (left !== void 0 && skippable(left)) {
610
+ visit(i + 1, j);
611
+ }
612
+ if (right !== void 0 && skippable(right)) {
613
+ visit(i, j + 1);
614
+ }
615
+ if (left !== void 0 && right !== void 0) {
616
+ if (meetOnOneSegment(left, right)) {
617
+ visit(
618
+ left.kind === "star" ? i : i + 1,
619
+ right.kind === "star" ? j : j + 1
620
+ );
621
+ }
622
+ }
623
+ }
624
+ return false;
625
+ }
626
+ function pathsMeet(a, b) {
627
+ const left = patternOf(a);
628
+ const right = patternOf(b);
629
+ return left.some((one) => right.some((other) => itemsMeet(one, other)));
630
+ }
631
+ function patternAdmits(declared, request) {
632
+ const literal = literalItemsOf(request);
633
+ return patternOf(declared).some((items) => itemsMeet(items, literal));
634
+ }
635
+ function pathSpansShapes(path6) {
636
+ return /\{[^{}]*[?+*]\}|\(|(?:^|\/)\*(?:\/|$)/.test(path6);
637
+ }
638
+ function countOf(items, kinds) {
639
+ return items.filter((item) => kinds.includes(item.kind)).length;
640
+ }
641
+ function readingSpecificity(items) {
642
+ return [
643
+ countOf(items, ["text"]),
644
+ countOf(items, ["shaped"]),
645
+ -countOf(items, ["optional", "star"])
646
+ ];
647
+ }
648
+ function segmentFit(route, request) {
649
+ if (!meetOnOneSegment(route, request)) {
650
+ return "apart";
651
+ }
652
+ if (request.kind === "text") {
653
+ if (route.kind === "text") {
654
+ return "stated";
655
+ }
656
+ return route.kind === "shaped" ? "shaped" : "free";
657
+ }
658
+ if (absorbsAnything(route)) {
659
+ return "free";
660
+ }
661
+ if (request.kind !== "shaped") {
662
+ return "guess";
663
+ }
664
+ if (route.kind === "shaped") {
665
+ return "free";
666
+ }
667
+ return route.kind === "text" && request.bare === route.text ? "stated" : "guess";
668
+ }
669
+ function shapedText(item) {
670
+ return item.kind === "shaped" ? item.textLength : 0;
671
+ }
672
+ function betterFit(a, b) {
673
+ return a === void 0 || compareRanks(b, a) > 0;
674
+ }
675
+ function bestFit(route, request) {
676
+ const width = request.length + 1;
677
+ const best = new Array((route.length + 1) * width);
678
+ best[0] = [0, 0];
679
+ const offer = (i, j, fit) => {
680
+ const at = i * width + j;
681
+ if (betterFit(best[at], fit)) {
682
+ best[at] = fit;
683
+ }
684
+ };
685
+ for (let reached = 0; reached <= route.length + request.length; reached += 1) {
686
+ for (let i = Math.max(0, reached - request.length); i <= Math.min(route.length, reached); i += 1) {
687
+ const j = reached - i;
688
+ const fit = best[i * width + j];
689
+ if (fit === void 0) {
690
+ continue;
691
+ }
692
+ const left = route[i];
693
+ const right = request[j];
694
+ if (left !== void 0 && skippable(left)) {
695
+ offer(i + 1, j, fit);
696
+ }
697
+ if (right !== void 0 && skippable(right)) {
698
+ offer(i, j + 1, fit);
699
+ }
700
+ if (left === void 0 || right === void 0) {
701
+ continue;
702
+ }
703
+ if (left.kind === "star" && right.kind === "star") {
704
+ continue;
705
+ }
706
+ const kind = segmentFit(left, right);
707
+ if (kind === "apart" || kind === "guess") {
708
+ continue;
709
+ }
710
+ offer(
711
+ left.kind === "star" ? i : i + 1,
712
+ right.kind === "star" ? j : j + 1,
713
+ [
714
+ fit[0] + (kind === "stated" ? 1 : 0),
715
+ fit[1] + (kind === "shaped" ? shapedText(left) : 0)
716
+ ]
717
+ );
718
+ }
719
+ }
720
+ return best[route.length * width + request.length] ?? null;
721
+ }
722
+ function requestRank(route, request) {
723
+ let found = null;
724
+ for (const routeItems of patternOf(route)) {
725
+ for (const requestItems of patternOf(request)) {
726
+ const fit = bestFit(routeItems, requestItems);
727
+ const rank = fit === null ? null : [...fit, ...readingSpecificity(routeItems)];
728
+ if (rank !== null && (found === null || compareRanks(rank, found) > 0)) {
729
+ found = rank;
730
+ }
731
+ }
732
+ }
733
+ return found;
734
+ }
735
+ function isCatchAll(route) {
736
+ return patternOf(route).some(
737
+ (items) => items.length > 0 && items.every(absorbsAnything) && items.some((item) => item.kind === "star")
738
+ );
739
+ }
740
+ function compareRanks(a, b) {
741
+ const length = Math.max(a.length, b.length);
742
+ for (let index = 0; index < length; index += 1) {
743
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
744
+ if (difference !== 0) {
745
+ return difference;
746
+ }
747
+ }
748
+ return 0;
749
+ }
750
+ var RestSemanticsSchema = z7.object({
751
+ name: z7.literal("rest"),
752
+ /**
753
+ * Uppercase HTTP method ("GET", "POST", …), `"*"` for a handler that
754
+ * responds to every method, or null when this source does not say
755
+ * which method.
756
+ */
757
+ method: z7.string().min(1).nullable(),
758
+ /** Normalized route path ("/users/{id}"), or null when this source does not give one. */
759
+ path: z7.string().min(1).nullable(),
760
+ /**
761
+ * Status codes the producing source declared explicitly, such as
762
+ * OpenAPI responses or CloudFormation MethodResponses. They are kept
763
+ * here so the pairing layer can see them without unwrapping any
764
+ * metadata. Absent or empty for an inferred source.
765
+ */
766
+ declaredResponses: z7.array(z7.number()).optional(),
767
+ /**
768
+ * The host a client wrote in an absolute URL, with its port
769
+ * (`api.example.com:8443`). A piece suss could not read is written
770
+ * `{name}`. Absent for a relative URL and for a route.
771
+ */
772
+ host: z7.string().min(1).optional(),
773
+ /**
774
+ * True for a route the framework also serves with `.{format}` after
775
+ * its path, the way Rails serves `/search.json` from `get "search"`.
776
+ */
777
+ optionalFormat: z7.boolean().optional()
778
+ });
779
+ var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
780
+ function baseUrlReference(semantics) {
781
+ const label = OPENING_HOLE.exec(semantics.path ?? "")?.[1];
782
+ return label === void 0 ? null : referenceFromName(patternHole(label));
783
+ }
784
+ function normalizePath(path6) {
785
+ let normalized = withoutParameterConstraints(path6).replace(
786
+ /:([a-zA-Z_]\w*)([?+*]?)/g,
787
+ "{$1$2}"
788
+ );
789
+ if (normalized.length > 1 && normalized.endsWith("/")) {
790
+ normalized = normalized.slice(0, -1);
791
+ }
792
+ normalized = normalized.replace(
793
+ /\{[^}]+\}|[^{]+/g,
794
+ (segment) => segment.startsWith("{") ? segment : segment.toLowerCase()
795
+ );
796
+ return normalized;
797
+ }
798
+ function withoutParameterConstraints(path6) {
799
+ let out = "";
800
+ let i = 0;
801
+ while (i < path6.length) {
802
+ const name = /^:[a-zA-Z_]\w*\(/.exec(path6.slice(i));
803
+ if (name === null) {
804
+ out += path6[i];
805
+ i += 1;
806
+ continue;
807
+ }
808
+ const open = i + name[0].length - 1;
809
+ const close = closingParenthesis(path6, open);
810
+ if (close === -1) {
811
+ return out + path6.slice(i);
812
+ }
813
+ out += path6.slice(i, open);
814
+ i = close + 1;
815
+ }
816
+ return out;
817
+ }
818
+ function closingParenthesis(text, open) {
819
+ let depth = 0;
820
+ for (let i = open; i < text.length; i += 1) {
821
+ const char = text[i];
822
+ if (char === "\\") {
823
+ i += 1;
824
+ continue;
825
+ }
826
+ if (char === "(") {
827
+ depth += 1;
828
+ }
829
+ if (char === ")") {
830
+ depth -= 1;
831
+ if (depth === 0) {
832
+ return i;
833
+ }
834
+ }
835
+ }
836
+ return -1;
837
+ }
838
+ function pathShape(path6) {
839
+ return normalizePath(path6).replace(/\{[^{}]*?([?+*]?)\}/g, "{$1}");
840
+ }
841
+ function methodsAgree(a, b) {
842
+ if (a === null || b === null) {
843
+ return false;
844
+ }
845
+ return a === b || a === "*" || b === "*";
846
+ }
847
+ function routePathAdmits(declaredPath, requestPath) {
848
+ return patternAdmits(normalizePath(declaredPath), normalizePath(requestPath));
849
+ }
850
+ function readHost(semantics) {
851
+ const host = semantics.host;
852
+ return host === void 0 || /[{(]/.test(host) ? void 0 : host;
853
+ }
854
+ function hostsAgree(a, b) {
855
+ return readHost(a) === readHost(b);
856
+ }
857
+ function hostUnread(semantics) {
858
+ if (semantics.host !== void 0) {
859
+ return readHost(semantics) === void 0;
860
+ }
861
+ return baseUrlReference(semantics) !== null;
862
+ }
863
+ function servedPath(semantics, path6) {
864
+ const normalized = normalizePath(path6);
865
+ return semantics.optionalFormat === true ? `${normalized}(|.{format})` : normalized;
866
+ }
867
+ function withoutHost(semantics) {
868
+ const { host: _host, ...rest } = semantics;
869
+ return rest;
870
+ }
871
+ function groundedBaseUrl(semantics, deployment) {
872
+ const reference = baseUrlReference(semantics);
873
+ if (reference === null) {
874
+ return null;
875
+ }
876
+ const base = deployment.setTo(reference);
877
+ if (base === null) {
878
+ return null;
879
+ }
880
+ const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
881
+ const grounded = pathAfterOrigin(`${base}${rest}`);
882
+ return { ...semantics, path: grounded === "" ? "/" : grounded };
883
+ }
884
+ var restSemantics = defineBoundarySemantics({
885
+ name: "rest",
886
+ schema: RestSemanticsSchema,
887
+ semconv: {
888
+ // A span states the one method a request used, so a route that
889
+ // responds to every method has nothing to compare.
890
+ method: { name: "http.request.method", placeholderValues: ["*"] },
891
+ path: { name: "http.route" }
892
+ // declaredResponses is a list, and http.response.status_code is
893
+ // the one status a request got back.
894
+ },
895
+ behavior: {
896
+ /** A request goes out, a status and a body come back. */
897
+ exchangesHttpResponses: true,
898
+ leavesTheProcess: true,
899
+ reportsUnpairedItself: false,
900
+ /** `"METHOD /normalized/path"`, or null when either half is missing. */
901
+ identityKey(semantics) {
902
+ if (semantics.method === null || semantics.path === null) {
903
+ return null;
904
+ }
905
+ return `${semantics.method.toUpperCase()} ${normalizePath(semantics.path)}`;
906
+ },
907
+ /**
908
+ * The bucket has only the path, so a `"*"` route lands with the
909
+ * consumers that each give a method, and `methodsAgree` compares the
910
+ * methods inside the bucket. The identity key is still what a reader
911
+ * sees and what a suppression targets ("GET /users", "* /users").
912
+ *
913
+ * Parameter names are dropped too. A hand-written spec writes
914
+ * `{userId}` where the Express route that serves it writes `:id`,
915
+ * and both match the same requests. Pairing on the name would leave
916
+ * two boundaries that never meet.
917
+ */
918
+ pairingKey(semantics) {
919
+ if (semantics.method === null || semantics.path === null) {
920
+ return null;
921
+ }
922
+ const shape = pathShape(pathAfterBaseUrl(normalizePath(semantics.path)));
923
+ const host = readHost(semantics);
924
+ return host === void 0 ? `rest ${shape}` : `rest //${host}${shape}`;
925
+ },
926
+ /**
927
+ * A spec writes `/teams/{team_id}` where the route that serves it
928
+ * writes `/teams/:id`. The method stays in, so a `"*"` handler is
929
+ * never compared with the document for one method.
930
+ */
931
+ operationKey(semantics) {
932
+ if (semantics.method === null || semantics.path === null) {
933
+ return null;
934
+ }
935
+ return `${semantics.method.toUpperCase()} ${pathShape(semantics.path)}`;
936
+ },
937
+ sidesAgree(a, b) {
938
+ return methodsAgree(a.method, b.method) && hostsAgree(a, b);
939
+ },
940
+ spansBuckets(semantics) {
941
+ return semantics.path !== null && (semantics.optionalFormat === true || pathSpansShapes(normalizePath(semantics.path)));
942
+ },
943
+ bucketsMeet(a, b) {
944
+ return a.path !== null && b.path !== null && hostsAgree(a, b) && pathsMeet(servedPath(a, a.path), servedPath(b, b.path));
945
+ },
946
+ /**
947
+ * A catch-all route never takes a call whose host suss could not
948
+ * read, since nothing else ties that call to this app.
949
+ */
950
+ pairRank(provider, consumer) {
951
+ if (provider.path === null || consumer.path === null) {
952
+ return [];
953
+ }
954
+ const route = normalizePath(provider.path);
955
+ if (hostUnread(consumer) && isCatchAll(route)) {
956
+ return null;
957
+ }
958
+ return requestRank(
959
+ servedPath(provider, provider.path),
960
+ normalizePath(consumer.path)
961
+ );
962
+ },
963
+ /**
964
+ * The identity key, with a missing half still readable: `ANY` when
965
+ * the source gave no method, and `?` when it gave no path. Null when
966
+ * both are missing, since then there is nothing to show.
967
+ */
968
+ displayLabel(semantics) {
969
+ if (semantics.method === null && semantics.path === null) {
970
+ return null;
971
+ }
972
+ const method = semantics.method === null ? "ANY" : semantics.method.toUpperCase();
973
+ const path6 = semantics.path === null ? "?" : normalizePath(semantics.path);
974
+ return `${method} ${path6}`;
975
+ },
976
+ /**
977
+ * With both halves present, the method goes through `methodsAgree`
978
+ * (so a `"*"` route matches every method) and the path through
979
+ * `routePathAdmits`. A route missing either half might still handle
980
+ * the request, and this declaration cannot settle that, so the
981
+ * result is `"unknown"`.
982
+ */
983
+ servesRequest(semantics, method, path6) {
984
+ if (semantics.method === null || semantics.path === null) {
985
+ return "unknown";
986
+ }
987
+ if (!methodsAgree(semantics.method, method.toUpperCase())) {
988
+ return "nomatch";
989
+ }
990
+ return routePathAdmits(semantics.path, path6) ? "match" : "nomatch";
991
+ },
992
+ /**
993
+ * A route the registration pattern admits is inside it. A route
994
+ * without a path counts as outside, because nothing shows that the
995
+ * pattern covers it.
996
+ */
997
+ withinScope(semantics, scope) {
998
+ return semantics.path !== null && routePathAdmits(scope, semantics.path);
999
+ },
1000
+ /**
1001
+ * A call whose base URL the deployment fills in, resolved to the
1002
+ * path it reaches, and a call to a host that can be the project's
1003
+ * own, with the host left out so it meets the route.
1004
+ *
1005
+ * The source cannot settle a base URL on its own, so the adapter
1006
+ * leaves the hole in. `API_BASE` could be `http://backend.internal`,
1007
+ * and then the path is `/orders`. It could equally be `/api/v2`, and
1008
+ * then the path is `/api/v2/orders`. Filling in the deployed value
1009
+ * and reading the path back out gets the right one either way, and
1010
+ * a base the deployment sets counts as the app's own.
1011
+ */
1012
+ /** A listed host matches with or without the port the call gives. */
1013
+ withoutOwnHost(semantics, ownHosts) {
1014
+ const host = semantics.host?.toLowerCase();
1015
+ if (host === void 0) {
1016
+ return null;
1017
+ }
1018
+ return ownHosts.has(host) || ownHosts.has(host.replace(/:\d+$/, "")) ? withoutHost(semantics) : null;
1019
+ },
1020
+ groundName(semantics, deployment) {
1021
+ const based = groundedBaseUrl(semantics, deployment);
1022
+ const grounded = based ?? semantics;
1023
+ const host = readHost(grounded);
1024
+ return host !== void 0 && hostCanBeOwn(host) ? withoutHost(grounded) : based;
1025
+ },
1026
+ nameReference: baseUrlReference,
1027
+ ruleBoundary: {
1028
+ // "METHOD /path": one token, a space, then an absolute path.
1029
+ claims(raw) {
1030
+ return /^\S+ +\//.test(raw.trim());
1031
+ },
1032
+ normalize(raw) {
1033
+ const trimmed = raw.trim();
1034
+ const spaceIdx = trimmed.indexOf(" ");
1035
+ const method = trimmed.slice(0, spaceIdx).toUpperCase();
1036
+ return `${method} ${normalizePath(trimmed.slice(spaceIdx + 1).trim())}`;
1037
+ }
1038
+ }
1039
+ }
1040
+ });
1041
+ var DeployableUnitSchema = z8.object({
1042
+ deploymentTarget: z8.enum([
1043
+ "lambda",
1044
+ "ecs-task",
1045
+ "container",
1046
+ "k8s-deployment",
1047
+ "worker"
1048
+ ]),
1049
+ // An empty name would agree with every other empty name, so a unit
1050
+ // with no name has to leave the field off instead.
1051
+ instanceName: z8.string().min(1)
1052
+ });
1053
+ var RuntimeConfigSemanticsSchema = DeployableUnitSchema.partial({
1054
+ deploymentTarget: true,
1055
+ instanceName: true
1056
+ }).extend({
1057
+ name: z9.literal("runtime-config")
1058
+ });
1059
+ var runtimeConfigSemantics = defineBoundarySemantics({
1060
+ name: "runtime-config",
1061
+ schema: RuntimeConfigSemanticsSchema,
1062
+ // service.name and cloud.resource_id identify the same deployable, but
1063
+ // instanceName is the template's logical id, a different string from
1064
+ // both.
1065
+ semconv: {},
1066
+ behavior: {
1067
+ /** A process reads its config at startup, and nothing replies. */
1068
+ exchangesHttpResponses: false,
1069
+ // Whoever deploys the process puts the value there, so the value
1070
+ // comes from outside the code.
1071
+ leavesTheProcess: true,
1072
+ reportsUnpairedItself: false,
1073
+ identityKey: () => null,
1074
+ // A reader sees which deployable's environment this is. Code that
1075
+ // reads a variable does not say which deployable runs it, so a read
1076
+ // has no name.
1077
+ displayLabel(semantics) {
1078
+ const instance = semantics.instanceName;
1079
+ return instance === void 0 ? null : `runtime-config:${instance}`;
1080
+ }
1081
+ }
1082
+ });
1083
+ var StorageSemanticsSchema = z10.object({
1084
+ name: z10.literal("storage"),
1085
+ /**
1086
+ * The storage system: an OpenTelemetry value such as `postgresql` or
1087
+ * `aws.dynamodb`, or suss's own word for one the conventions do not
1088
+ * cover, such as `s3`. Null when the reader could not work out which
1089
+ * system the source uses. Two products' containers can share a name,
1090
+ * and this field keeps them apart. The pairing pass decides how to
1091
+ * treat a null.
1092
+ */
1093
+ storageSystem: z10.string().nullable(),
1094
+ /**
1095
+ * The ORM, schema, or deployment scope. A single-database setup uses
1096
+ * `"default"`, and a monorepo with several schemas gives each one its
1097
+ * own value.
1098
+ */
1099
+ scope: z10.string(),
1100
+ /**
1101
+ * The table, bucket, collection or index, as the source declares it.
1102
+ * Null when the reader could not work out the container, and a null
1103
+ * container pairs with nothing, so it cannot match by accident of
1104
+ * shared source text. The string uses the boundary-name syntax (a
1105
+ * literal, a pattern with `{}` holes, or a reference), and only
1106
+ * `parseBoundaryName` should read it.
1107
+ */
1108
+ container: z10.string().nullable(),
1109
+ /**
1110
+ * A secondary way into the container, such as a DynamoDB global
1111
+ * secondary index or an Elasticsearch alias, each with its own key
1112
+ * fields. Null means the container's primary key. A query through an
1113
+ * index and a query through the table are different accesses, so
1114
+ * they pair separately.
1115
+ */
1116
+ accessPath: z10.string().nullable()
1117
+ });
1118
+ function containerReference(semantics) {
1119
+ return semantics.container === null ? null : referenceFromName(semantics.container);
1120
+ }
1121
+ var storageSemantics = defineBoundarySemantics({
1122
+ name: "storage",
1123
+ schema: StorageSemanticsSchema,
1124
+ semconv: {
1125
+ storageSystem: { name: "db.system.name" },
1126
+ // suss writes "default" when the source did not say which database.
1127
+ scope: { name: "db.namespace", placeholderValues: ["default"] },
1128
+ container: { name: "db.collection.name" }
1129
+ // The conventions have no attribute for a secondary index, so
1130
+ // accessPath is left out.
1131
+ },
1132
+ behavior: {
1133
+ /** A query returns rows or items, with no status. */
1134
+ exchangesHttpResponses: false,
1135
+ leavesTheProcess: true,
1136
+ reportsUnpairedItself: false,
1137
+ identityKey: () => null,
1138
+ displayLabel: storageLabel,
1139
+ /**
1140
+ * A container the source gives as a variable is whatever the
1141
+ * deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
1142
+ * code and `prod-subscribers-v1` in the manifest are one table.
1143
+ */
1144
+ groundName(semantics, deployment) {
1145
+ const reference = containerReference(semantics);
1146
+ if (reference === null) {
1147
+ return null;
1148
+ }
1149
+ const name = deployment.setTo(reference);
1150
+ return name === null ? null : { ...semantics, container: name };
1151
+ },
1152
+ nameReference: containerReference
1153
+ }
1154
+ });
1155
+ function storageLabel(semantics) {
1156
+ return `${storageSystemLabel(semantics)}:${storageContainerLabel(semantics)}`;
1157
+ }
1158
+ function storageSystemLabel(semantics) {
1159
+ return semantics.storageSystem ?? UNKNOWN_ENGINE;
1160
+ }
1161
+ var UNKNOWN_ENGINE = "<unknown engine>";
1162
+ function storageContainerLabel(semantics) {
1163
+ const container = semantics.container ?? "<unnamed container>";
1164
+ const addressed = semantics.accessPath === null ? container : `${container}#${semantics.accessPath}`;
1165
+ return semantics.scope === "default" ? addressed : `${semantics.scope}/${addressed}`;
1166
+ }
1167
+ var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
1168
+ name: z11.literal("unit-invocation"),
1169
+ instanceName: DeployableUnitSchema.shape.instanceName.nullable()
1170
+ });
1171
+ function calleeReference(semantics) {
1172
+ return semantics.instanceName === null ? null : referenceFromName(semantics.instanceName);
1173
+ }
1174
+ var unitInvocationSemantics = defineBoundarySemantics({
1175
+ name: "unit-invocation",
1176
+ schema: UnitInvocationSemanticsSchema,
1177
+ // faas.name is the deployed function's own name and instanceName is
1178
+ // the deployment template's logical id, which is a different string.
1179
+ // Runtime config leaves the pair off for the same reason.
1180
+ semconv: {},
1181
+ behavior: {
1182
+ /**
1183
+ * A synchronous invoke gives back whatever the handler returned,
1184
+ * which is a value rather than a status and a body. A handler's
1185
+ * return only becomes an HTTP response under a proxy integration,
1186
+ * and that route gets a REST binding instead of this one.
1187
+ */
1188
+ exchangesHttpResponses: false,
1189
+ leavesTheProcess: true,
1190
+ /**
1191
+ * A unit nothing in the run invokes is the ordinary case, since
1192
+ * most services are reached from a stack suss never read. So an
1193
+ * uninvoked unit stays in the generic unmatched list rather than
1194
+ * being reported as a problem here.
1195
+ */
1196
+ reportsUnpairedItself: false,
1197
+ /**
1198
+ * A callee the source gives only as a variable has no name until the
1199
+ * deployment fills it in. It gets no key, since a key built from the
1200
+ * variable would pair only with itself.
1201
+ */
1202
+ identityKey(semantics) {
1203
+ if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
1204
+ return null;
1205
+ }
1206
+ return unitIdentityKey(
1207
+ semantics.deploymentTarget,
1208
+ semantics.instanceName
1209
+ );
1210
+ },
1211
+ displayLabel(semantics) {
1212
+ if (semantics.instanceName === null) {
1213
+ return `unit:${semantics.deploymentTarget} (named at runtime)`;
1214
+ }
1215
+ return unitIdentityKey(
1216
+ semantics.deploymentTarget,
1217
+ semantics.instanceName
1218
+ );
1219
+ },
1220
+ /**
1221
+ * A call that reads its callee's name from the environment reaches
1222
+ * whichever resource the template wires that variable to. The name
1223
+ * is grounded to the logical id and not the deployed name, because
1224
+ * the invoked unit's own summary is keyed by the logical id.
1225
+ */
1226
+ groundName(semantics, deployment) {
1227
+ const reference = calleeReference(semantics);
1228
+ if (reference === null) {
1229
+ return null;
1230
+ }
1231
+ const logicalId = deployment.pointsAt(reference);
1232
+ return logicalId === null ? null : { ...semantics, instanceName: logicalId };
1233
+ },
1234
+ nameReference: calleeReference
1235
+ }
1236
+ });
1237
+ var SemanticsSchema = z12.discriminatedUnion("name", [
1238
+ restSemantics.schema,
1239
+ functionCallSemantics.schema,
1240
+ graphqlResolverSemantics.schema,
1241
+ graphqlOperationSemantics.schema,
1242
+ runtimeConfigSemantics.schema,
1243
+ storageSemantics.schema,
1244
+ messageBusSemantics.schema,
1245
+ metricSemantics.schema,
1246
+ unitInvocationSemantics.schema,
1247
+ ioSemantics.schema
1248
+ ]);
1249
+ var DEFINITIONS = [
1250
+ restSemantics,
1251
+ functionCallSemantics,
1252
+ graphqlResolverSemantics,
1253
+ graphqlOperationSemantics,
1254
+ runtimeConfigSemantics,
1255
+ storageSemantics,
1256
+ messageBusSemantics,
1257
+ metricSemantics,
1258
+ unitInvocationSemantics,
1259
+ ioSemantics
1260
+ ];
1261
+ var BY_NAME = new Map(
1262
+ DEFINITIONS.map((d) => [d.name, d])
1263
+ );
1264
+ var ConfidenceSourceSchema = z13.enum([
1265
+ "inferred_static",
1266
+ "inferred_ai",
1267
+ "declared",
1268
+ "derived"
1269
+ ]);
1270
+ var ConfidenceLevelSchema = z13.enum(["high", "medium", "low"]);
1271
+ var CorroborationSchema = z13.object({
1272
+ outcome: z13.enum(["observed", "refuted", "untested"]),
1273
+ /** Executions that produced a verdict for this claim. */
1274
+ runs: z13.number(),
1275
+ /** Present when refuted: the input and observation that disagreed. */
1276
+ counterexample: z13.unknown().optional(),
1277
+ /** Present when untested: why no verdict was reachable. */
1278
+ reason: z13.string().optional()
1279
+ });
1280
+ var ConfidenceInfoSchema = z13.object({
1281
+ source: ConfidenceSourceSchema,
1282
+ level: ConfidenceLevelSchema,
1283
+ corroboration: CorroborationSchema.optional()
1284
+ });
1285
+ var SourceLocationSchema = z13.object({
1286
+ file: z13.string(),
1287
+ /** Line numbers, for a person reading the summary or an editor link. */
1288
+ range: z13.object({ start: z13.number(), end: z13.number() }),
1289
+ /**
1290
+ * Character offsets of the unit in its file. Identity and joins use
1291
+ * these, because two functions can share a line but never an offset
1292
+ * range. `range` gives the same place in lines. Absent on a summary
1293
+ * with no source position, such as one read from a contract.
1294
+ */
1295
+ span: z13.object({ start: z13.number(), end: z13.number() }).optional(),
1296
+ exportName: z13.string().nullable(),
1297
+ /**
1298
+ * The project the extract ran on, by the name the project gives
1299
+ * itself.
1300
+ *
1301
+ * Paths are relative to wherever the extract ran, so two services in
1302
+ * one repository can both report `src/handlers.ts`, and merging their
1303
+ * summaries would mix them up. This field tells them apart, and a
1304
+ * reader groups by it.
1305
+ */
1306
+ workspace: z13.string().optional(),
1307
+ /**
1308
+ * The module of the application the unit belongs to, by the name the
1309
+ * project's `suss.json` gives it. Set only when the project lists its
1310
+ * modules, and then on every unit whose file is under a module's root.
1311
+ */
1312
+ module: z13.string().optional()
1313
+ });
1314
+ var BoundaryBindingSchema = z13.object({
1315
+ transport: z13.string(),
1316
+ semantics: SemanticsSchema,
1317
+ recognition: z13.string()
1318
+ });
1319
+ var TypeShapeSchema = z13.lazy(
1320
+ () => z13.discriminatedUnion("type", [
1321
+ z13.object({
1322
+ type: z13.literal("record"),
1323
+ properties: z13.record(z13.string(), TypeShapeSchema),
1324
+ spreads: z13.array(z13.object({ sourceText: z13.string() })).optional()
1325
+ }),
1326
+ z13.object({ type: z13.literal("dictionary"), values: TypeShapeSchema }),
1327
+ z13.object({ type: z13.literal("array"), items: TypeShapeSchema }),
1328
+ z13.object({
1329
+ type: z13.literal("literal"),
1330
+ value: z13.union([z13.string(), z13.number(), z13.boolean()]),
1331
+ raw: z13.string().optional()
1332
+ }),
1333
+ z13.object({ type: z13.literal("text") }),
1334
+ z13.object({ type: z13.literal("integer") }),
1335
+ z13.object({ type: z13.literal("number") }),
1336
+ z13.object({ type: z13.literal("boolean") }),
1337
+ z13.object({ type: z13.literal("null") }),
1338
+ z13.object({ type: z13.literal("undefined") }),
1339
+ z13.object({
1340
+ type: z13.literal("union"),
1341
+ variants: z13.array(TypeShapeSchema)
1342
+ }),
1343
+ z13.object({
1344
+ type: z13.literal("ref"),
1345
+ name: z13.string(),
1346
+ from: z13.string().optional(),
1347
+ def: z13.string().optional()
1348
+ }),
1349
+ z13.object({ type: z13.literal("unknown") })
1350
+ ])
1351
+ );
1352
+
1353
+ // ../ir-core/dist/index.js
1354
+ import { z as z14 } from "zod";
1355
+ import { z as z22 } from "zod";
1356
+ var RelationSchema = z14.enum([
1357
+ "provides",
1358
+ "reads",
1359
+ "writes",
1360
+ "invokes"
1361
+ ]);
1362
+ var EffectRelationSchema = RelationSchema.exclude(["provides"]);
1363
+ var SuppressionSideSchema = z22.object({
1364
+ summary: z22.string().optional(),
1365
+ transitionId: z22.string().optional(),
1366
+ /** The field on that side, for a finding about one field and no transition. */
1367
+ field: z22.string().optional()
1368
+ }).optional();
1369
+ var SuppressionRuleSchema = z22.object({
1370
+ /**
1371
+ * The finding kind to match, behavioral or intent. The schema accepts
1372
+ * any string because ir-core cannot import either IR's kind enum. The
1373
+ * CLI's loader checks it against both.
1374
+ */
1375
+ kind: z22.string().optional(),
1376
+ /**
1377
+ * The boundary's human-readable key, such as "GET /pet/{petId}" or
1378
+ * "fn:@acme/api::getUser". A REST key goes through the same path
1379
+ * normalizer the checkers use, so `:id` and `{id}` compare equal.
1380
+ */
1381
+ boundary: z22.string().optional(),
1382
+ /**
1383
+ * Consumer-side discriminators, the narrowest useful match. They
1384
+ * apply only to behavioral findings. A rule that sets `consumer`
1385
+ * never matches an intent finding, because an intent finding has no
1386
+ * consumer side.
1387
+ */
1388
+ consumer: SuppressionSideSchema,
1389
+ /**
1390
+ * Provider-side discriminators, matching the same fields as
1391
+ * `consumer`. A finding about a status the provider produces has its
1392
+ * transition id on this side, and that id is the only value narrow
1393
+ * enough to pick out that one finding. As with `consumer`, a rule
1394
+ * that sets `provider` never matches an intent finding.
1395
+ */
1396
+ provider: SuppressionSideSchema,
1397
+ /**
1398
+ * The title of one PRD scenario. A PRD finding's boundary is the
1399
+ * whole PRD, so without this a rule accepts every scenario in it,
1400
+ * including one added later. A rule that sets `scenario` matches
1401
+ * only intent findings about a scenario with that title.
1402
+ */
1403
+ scenario: z22.string().optional(),
1404
+ /**
1405
+ * "narrow", the default, requires `kind` plus one of `boundary`,
1406
+ * `scenario`, `consumer.transitionId` or `provider.transitionId`, so
1407
+ * the rule targets a specific class of finding. "broad" allows a
1408
+ * match on kind alone or boundary alone, and such a rule also
1409
+ * silences future regressions in that category.
1410
+ */
1411
+ scope: z22.enum(["narrow", "broad"]).default("narrow"),
1412
+ /** Required human-written justification. */
1413
+ reason: z22.string().min(1),
1414
+ /** What to do when a finding matches. */
1415
+ effect: z22.enum(["mark", "downgrade", "hide"]).default("mark")
1416
+ }).strict();
1417
+ var SuppressionFileSchema = z22.object({
1418
+ version: z22.literal(1),
1419
+ rules: z22.array(SuppressionRuleSchema)
1420
+ }).strict();
1421
+
1422
+ // ../framework/rails/dist/index.js
1423
+ var ADDED_PATHS = /config\.(?:eager_load_paths|autoload_paths|autoload_once_paths)\s*(?:<<|\+=|\.push\(|\.unshift\(|\.concat\()\s*([^\n]*)/g;
1424
+ var ROOT_JOIN = /Rails\.root\.join\(\s*([^)]*)\)/g;
1425
+ var INTERPOLATED_ROOT = /#\{(?:Rails\.root|config\.root|root)\}\/([\w./-]+)/g;
1426
+ var AUTOLOAD_LIB = /config\.autoload_lib(?:_once)?\b/;
1427
+ var EVERY_LITERAL = /(["'])([^"']*)\1/g;
1428
+ function applicationFile(configDirectory) {
1429
+ const file = applicationPath(configDirectory);
1430
+ return fs.existsSync(file) ? [file] : [];
1431
+ }
1432
+ function applicationPath(configDirectory) {
1433
+ return path.join(configDirectory, "config", "application.rb");
1434
+ }
1435
+ function readAutoloadRoots(configDirectory) {
1436
+ const file = applicationPath(configDirectory);
1437
+ let source;
1438
+ try {
1439
+ source = fs.readFileSync(file, "utf8");
1440
+ } catch {
1441
+ return [];
1442
+ }
1443
+ const relative = [];
1444
+ for (const line of source.split("\n")) {
1445
+ if (line.trimStart().startsWith("#")) {
1446
+ continue;
1447
+ }
1448
+ if (AUTOLOAD_LIB.test(line)) {
1449
+ relative.push("lib");
1450
+ }
1451
+ for (const added of line.matchAll(ADDED_PATHS)) {
1452
+ relative.push(...directoriesIn(added[1] ?? ""));
1453
+ }
1454
+ }
1455
+ return [...new Set(relative)].map((directory) => path.resolve(configDirectory, directory)).filter((directory) => isDirectory(directory));
1456
+ }
1457
+ function directoriesIn(fragment) {
1458
+ const joined = [...fragment.matchAll(ROOT_JOIN)].map(
1459
+ (join) => [...(join[1] ?? "").matchAll(EVERY_LITERAL)].map((literal) => literal[2] ?? "").join("/")
1460
+ );
1461
+ const interpolated = [...fragment.matchAll(INTERPOLATED_ROOT)].map(
1462
+ (match) => match[1] ?? ""
1463
+ );
1464
+ return [...joined, ...interpolated].filter((one) => one !== "");
1465
+ }
1466
+ function isDirectory(directory) {
1467
+ try {
1468
+ return fs.statSync(directory).isDirectory();
1469
+ } catch {
1470
+ return false;
1471
+ }
1472
+ }
27
1473
  var ENGINE_BASE_NAMES = /* @__PURE__ */ new Set(["Rails::Engine", "::Rails::Engine"]);
28
1474
  var CONSTANT_TYPES = /* @__PURE__ */ new Set(["constant", "scope_resolution"]);
29
1475
  function readEngines(roots, acronyms = []) {
@@ -31,7 +1477,7 @@ function readEngines(roots, acronyms = []) {
31
1477
  for (const root of roots) {
32
1478
  const routesFile = engineRoutesFile(root);
33
1479
  for (const file of engineFilesUnder(root)) {
34
- const source = fs.readFileSync(file, "utf8");
1480
+ const source = fs2.readFileSync(file, "utf8");
35
1481
  const rootNode = parseRubySync(source).rootNode;
36
1482
  for (const found of engineClassesIn(rootNode, acronyms)) {
37
1483
  engines.push({ ...found, routesFile });
@@ -48,48 +1494,45 @@ function engineSourceFiles(root) {
48
1494
  ];
49
1495
  }
50
1496
  function engineRoutesFile(root) {
51
- const routesFile = path.join(root, "config", "routes.rb");
52
- return fs.existsSync(routesFile) ? routesFile : null;
1497
+ const routesFile = path2.join(root, "config", "routes.rb");
1498
+ return fs2.existsSync(routesFile) ? routesFile : null;
53
1499
  }
54
1500
  function engineFilesUnder(root) {
55
- const directory = path.join(root, "lib");
56
- if (!fs.existsSync(directory)) {
1501
+ const directory = path2.join(root, "lib");
1502
+ if (!fs2.existsSync(directory)) {
57
1503
  return [];
58
1504
  }
59
1505
  return rubyFilesUnder(directory).filter(
60
- (file) => fs.readFileSync(file, "utf8").includes("Rails::Engine")
1506
+ (file) => fs2.readFileSync(file, "utf8").includes("Rails::Engine")
61
1507
  );
62
1508
  }
63
1509
  function expandPathPattern(pattern) {
64
- const segments = pattern.split(path.sep).filter((s) => s !== "");
65
- const start = path.isAbsolute(pattern) ? path.sep : ".";
1510
+ const segments = pattern.split(path2.sep).filter((s) => s !== "");
1511
+ const start = path2.isAbsolute(pattern) ? path2.sep : ".";
66
1512
  let candidates = [start];
67
1513
  for (const segment of segments) {
68
1514
  if (!segment.includes("*")) {
69
- candidates = candidates.map((base) => path.join(base, segment));
1515
+ candidates = candidates.map((base) => path2.join(base, segment));
70
1516
  continue;
71
1517
  }
72
1518
  const matcher = new RegExp(
73
1519
  `^${segment.split("*").map(escapeRegExp).join(".*")}$`
74
1520
  );
75
1521
  candidates = candidates.flatMap(
76
- (base) => fs.existsSync(base) ? fs.readdirSync(base).filter((name) => matcher.test(name)).sort().map((name) => path.join(base, name)) : []
1522
+ (base) => fs2.existsSync(base) ? fs2.readdirSync(base).filter((name) => matcher.test(name)).sort().map((name) => path2.join(base, name)) : []
77
1523
  );
78
1524
  }
79
- return candidates.filter((candidate) => fs.existsSync(candidate));
1525
+ return candidates.filter((candidate) => fs2.existsSync(candidate));
80
1526
  }
81
1527
  function escapeRegExp(text) {
82
1528
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83
1529
  }
84
1530
  function rubyFilesUnder(directory) {
85
- return fs.readdirSync(directory, { withFileTypes: true, recursive: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".rb")).map((entry) => path.join(entry.parentPath, entry.name)).sort();
1531
+ return fs2.readdirSync(directory, { withFileTypes: true, recursive: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".rb")).map((entry) => path2.join(entry.parentPath, entry.name)).sort();
86
1532
  }
87
1533
  function engineClassesIn(node, acronyms, enclosing = []) {
88
1534
  const found = [];
89
- for (const child of node.namedChildren) {
90
- if (child === null) {
91
- continue;
92
- }
1535
+ for (const child of children(node)) {
93
1536
  if (child.type === "class" && extendsEngine(child)) {
94
1537
  const name = definedName(child);
95
1538
  if (name !== void 0) {
@@ -110,10 +1553,10 @@ function definedName(definition) {
110
1553
  }
111
1554
  function extendsEngine(classNode) {
112
1555
  const superclass = field(classNode, "superclass");
113
- const base = superclass?.namedChildren.find(
114
- (child) => child !== null && CONSTANT_TYPES.has(child.type)
1556
+ const base = children(superclass).find(
1557
+ (child) => CONSTANT_TYPES.has(child.type)
115
1558
  );
116
- return base !== void 0 && base !== null && ENGINE_BASE_NAMES.has(base.text);
1559
+ return base !== void 0 && ENGINE_BASE_NAMES.has(base.text);
117
1560
  }
118
1561
  function isolatedNamespaceOf(classNode, acronyms) {
119
1562
  const body = field(classNode, "body");
@@ -137,7 +1580,7 @@ var IRREGULAR_CALL = /\.irregular\s*\(?\s*(["'])([^"']*)\1\s*,\s*(["'])([^"']*)\
137
1580
  var UNCOUNTABLE_CALL = /\.uncountable\s*\(?\s*([^\n]*)/g;
138
1581
  var SINGULAR_CALL = /\.singular\s*\(?\s*(?:(["'])([^"']*)\1|\/([^/]*)\/([a-z]*))\s*,\s*(["'])([^"']*)\5/g;
139
1582
  var WORD_LIST = /%[wi][([{]([^)\]}]*)[)\]}]/;
140
- var EVERY_LITERAL = /(["'])([^"']*)\1/g;
1583
+ var EVERY_LITERAL2 = /(["'])([^"']*)\1/g;
141
1584
  var ANY_CALL = /\.(acronym|irregular|uncountable|singular)[\s("'/]/;
142
1585
  function group(match, at) {
143
1586
  return match[at] ?? "";
@@ -148,7 +1591,7 @@ function readInflections(configDirectory) {
148
1591
  const uncountable = /* @__PURE__ */ new Set();
149
1592
  const singular = [];
150
1593
  for (const file of inflectionFiles(configDirectory)) {
151
- const source = uncommented(fs2.readFileSync(file, "utf8"));
1594
+ const source = uncommented(fs3.readFileSync(file, "utf8"));
152
1595
  for (const match of source.matchAll(ACRONYM_CALL)) {
153
1596
  acronyms.add(group(match, 2));
154
1597
  }
@@ -182,18 +1625,130 @@ function wordsIn(argument) {
182
1625
  if (list !== null) {
183
1626
  return group(list, 1).split(/\s+/).filter((word) => word.length > 0);
184
1627
  }
185
- return [...argument.matchAll(EVERY_LITERAL)].map((match) => group(match, 2));
1628
+ return [...argument.matchAll(EVERY_LITERAL2)].map((match) => group(match, 2));
186
1629
  }
187
1630
  function inflectionFiles(configDirectory) {
188
- const directory = path2.join(configDirectory, "config", "initializers");
189
- if (!fs2.existsSync(directory)) {
1631
+ const directory = path3.join(configDirectory, "config", "initializers");
1632
+ if (!fs3.existsSync(directory)) {
190
1633
  return [];
191
1634
  }
192
- return fs2.readdirSync(directory, { withFileTypes: true, recursive: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".rb")).map((entry) => path2.join(entry.parentPath, entry.name)).filter((file) => ANY_CALL.test(uncommented(fs2.readFileSync(file, "utf8")))).sort();
1635
+ return fs3.readdirSync(directory, { withFileTypes: true, recursive: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".rb")).map((entry) => path3.join(entry.parentPath, entry.name)).filter((file) => ANY_CALL.test(uncommented(fs3.readFileSync(file, "utf8")))).sort();
193
1636
  }
194
1637
  function uncommented(source) {
195
1638
  return source.split("\n").filter((line) => !line.trimStart().startsWith("#")).join("\n");
196
1639
  }
1640
+ var EXPANSION_CAP = 64;
1641
+ function closingParenthesis2(text, open) {
1642
+ let depth = 0;
1643
+ for (let index = open; index < text.length; index += 1) {
1644
+ const char = text[index];
1645
+ if (char === "(") {
1646
+ depth += 1;
1647
+ }
1648
+ if (char === ")") {
1649
+ depth -= 1;
1650
+ if (depth === 0) {
1651
+ return index;
1652
+ }
1653
+ }
1654
+ }
1655
+ return -1;
1656
+ }
1657
+ function expansions(text) {
1658
+ let found = [""];
1659
+ let index = 0;
1660
+ while (index < text.length) {
1661
+ const open = text.indexOf("(", index);
1662
+ const close = text.indexOf(")", index);
1663
+ if (close !== -1 && (open === -1 || close < open)) {
1664
+ return null;
1665
+ }
1666
+ if (open === -1) {
1667
+ const rest = text.slice(index);
1668
+ found = found.map((prefix) => prefix + rest);
1669
+ break;
1670
+ }
1671
+ const end = closingParenthesis2(text, open);
1672
+ const inner = end === -1 ? null : expansions(text.slice(open + 1, end));
1673
+ if (inner === null) {
1674
+ return null;
1675
+ }
1676
+ const before = text.slice(index, open);
1677
+ const options = ["", ...inner];
1678
+ found = found.flatMap(
1679
+ (prefix) => options.map((option) => prefix + before + option)
1680
+ );
1681
+ if (found.length > EXPANSION_CAP) {
1682
+ found = found.slice(0, 1);
1683
+ }
1684
+ index = end + 1;
1685
+ }
1686
+ return found;
1687
+ }
1688
+ function tidied(path6) {
1689
+ const squeezed = `/${path6}`.replace(/\/{2,}/g, "/");
1690
+ return squeezed.length > 1 ? squeezed.replace(/\/$/, "") : squeezed;
1691
+ }
1692
+ function segmentsOf2(path6) {
1693
+ return path6 === "/" ? [] : path6.slice(1).split("/");
1694
+ }
1695
+ function sharedCount(lists, limit, fromEnd) {
1696
+ const first = lists[0] ?? [];
1697
+ const at = (list, offset) => fromEnd ? list[list.length - 1 - offset] : list[offset];
1698
+ let count = 0;
1699
+ while (count < limit && lists.every((list) => at(list, count) === at(first, count))) {
1700
+ count += 1;
1701
+ }
1702
+ return count;
1703
+ }
1704
+ var ENDS_IN_PARAMETER = /:[A-Za-z_]\w*$/;
1705
+ function headBeforeSet(lists, shortest) {
1706
+ const first = lists[0] ?? [];
1707
+ let count = sharedCount(lists, shortest, false);
1708
+ while (count > 0 && ENDS_IN_PARAMETER.test(first[count - 1] ?? "")) {
1709
+ count -= 1;
1710
+ }
1711
+ return count;
1712
+ }
1713
+ function asOnePath(paths) {
1714
+ const lists = paths.map(segmentsOf2);
1715
+ const shortest = Math.min(...lists.map((list) => list.length));
1716
+ const headCount = headBeforeSet(lists, shortest);
1717
+ const tailCount = sharedCount(lists, shortest - headCount, true);
1718
+ const first = lists[0] ?? [];
1719
+ const head = first.slice(0, headCount).join("/");
1720
+ const tail = first.slice(first.length - tailCount).join("/");
1721
+ const middles = lists.map(
1722
+ (list) => list.slice(headCount, list.length - tailCount).join("/")
1723
+ );
1724
+ if (head !== "") {
1725
+ const set2 = setPiece(middles.map((middle) => middle ? `/${middle}` : ""));
1726
+ return set2 === null ? null : `/${head}${set2}${tail ? `/${tail}` : ""}`;
1727
+ }
1728
+ const set = setPiece(
1729
+ middles.map((middle) => middle && tail ? `${middle}/` : middle)
1730
+ );
1731
+ return set === null ? null : `/${set}${tail}`;
1732
+ }
1733
+ function withGlobsAsHoles(path6) {
1734
+ return path6.replace(/(^|[/(])\*([A-Za-z_]\w*)/g, "$1:$2+");
1735
+ }
1736
+ function pathWithOptionalGroups(written) {
1737
+ const path6 = withGlobsAsHoles(written);
1738
+ if (!path6.includes("(")) {
1739
+ return path6;
1740
+ }
1741
+ const expanded = expansions(path6);
1742
+ if (expanded === null) {
1743
+ return path6;
1744
+ }
1745
+ const paths = [...new Set(expanded.map(tidied))];
1746
+ const withoutGroups = paths[0] ?? path6;
1747
+ if (paths.length === 1) {
1748
+ return withoutGroups;
1749
+ }
1750
+ return asOnePath(paths) ?? withoutGroups;
1751
+ }
197
1752
  function readSimpleArgs(call, defaults = {}) {
198
1753
  const argumentList = field2(call, "arguments");
199
1754
  const positional = [];
@@ -261,23 +1816,33 @@ function readSymbolList(node) {
261
1816
  const single = symbolValue(node);
262
1817
  return single === null ? null : [single];
263
1818
  }
1819
+ var updateAt = (path6) => [
1820
+ { method: "PATCH", path: path6 },
1821
+ { method: "PUT", path: path6 }
1822
+ ];
264
1823
  var RESTFUL_ROUTES_PLURAL = {
265
- index: (base) => ({ method: "GET", path: base }),
266
- create: (base) => ({ method: "POST", path: base }),
267
- new: (base) => ({ method: "GET", path: `${base}/new` }),
268
- show: (base) => ({ method: "GET", path: `${base}/:id` }),
269
- edit: (base) => ({ method: "GET", path: `${base}/:id/edit` }),
270
- update: (base) => ({ method: "PATCH", path: `${base}/:id` }),
271
- destroy: (base) => ({ method: "DELETE", path: `${base}/:id` })
1824
+ index: (base) => [{ method: "GET", path: base }],
1825
+ create: (base) => [{ method: "POST", path: base }],
1826
+ new: (base) => [{ method: "GET", path: `${base}/new` }],
1827
+ show: (base) => [{ method: "GET", path: `${base}/:id` }],
1828
+ edit: (base) => [{ method: "GET", path: `${base}/:id/edit` }],
1829
+ update: (base) => updateAt(`${base}/:id`),
1830
+ destroy: (base) => [{ method: "DELETE", path: `${base}/:id` }]
272
1831
  };
273
1832
  var RESTFUL_ROUTES_SINGULAR = {
274
- create: (base) => ({ method: "POST", path: base }),
275
- new: (base) => ({ method: "GET", path: `${base}/new` }),
276
- show: (base) => ({ method: "GET", path: base }),
277
- edit: (base) => ({ method: "GET", path: `${base}/edit` }),
278
- update: (base) => ({ method: "PATCH", path: base }),
279
- destroy: (base) => ({ method: "DELETE", path: base })
1833
+ create: (base) => [{ method: "POST", path: base }],
1834
+ new: (base) => [{ method: "GET", path: `${base}/new` }],
1835
+ show: (base) => [{ method: "GET", path: base }],
1836
+ edit: (base) => [{ method: "GET", path: `${base}/edit` }],
1837
+ update: (base) => updateAt(base),
1838
+ destroy: (base) => [{ method: "DELETE", path: base }]
280
1839
  };
1840
+ function restfulRoutes(action, base) {
1841
+ if (!Object.hasOwn(RESTFUL_ROUTES_PLURAL, action)) {
1842
+ return [];
1843
+ }
1844
+ return RESTFUL_ROUTES_PLURAL[action]?.(base) ?? [];
1845
+ }
281
1846
  function pluralize(word) {
282
1847
  if (/[^aeiou]y$/.test(word)) {
283
1848
  return `${word.slice(0, -1)}ies`;
@@ -317,12 +1882,19 @@ var RouteAccumulator = class {
317
1882
  mounting = /* @__PURE__ */ new Set();
318
1883
  /** The file whose statements are being walked, as a gap message prints it. */
319
1884
  file = "";
320
- /** The first route written for an action wins, because Rails matches routes in the order they are declared. */
1885
+ /** An action routed from several places serves every one of them, so each distinct route is kept. */
321
1886
  add(controllerKey, action, route) {
322
1887
  const key = `${controllerKey}#${action}`;
323
- if (!this.byKey.has(key)) {
324
- this.byKey.set(key, route);
1888
+ const routes = this.byKey.get(key) ?? [];
1889
+ const path6 = pathWithOptionalGroups(route.path);
1890
+ if (!routes.some((one) => one.method === route.method && one.path === path6)) {
1891
+ routes.push({
1892
+ method: route.method,
1893
+ path: path6,
1894
+ ...route.formatless === true ? { formatless: true } : {}
1895
+ });
325
1896
  }
1897
+ this.byKey.set(key, routes);
326
1898
  }
327
1899
  recordUnread(callName) {
328
1900
  addUnderFile(this.unread, this.file, callName);
@@ -333,8 +1905,8 @@ var RouteAccumulator = class {
333
1905
  recordMissingDrawn(name) {
334
1906
  this.missingDrawn.push({ file: this.file, name });
335
1907
  }
336
- routeFor(controllerKey, action) {
337
- return this.byKey.get(`${controllerKey}#${action}`) ?? null;
1908
+ routesFor(controllerKey, action) {
1909
+ return this.byKey.get(`${controllerKey}#${action}`) ?? [];
338
1910
  }
339
1911
  /** Runs `walk` with gaps attributed to `file`, then goes back to the file being walked before. */
340
1912
  inFile(file, walk) {
@@ -359,7 +1931,7 @@ var RouteAccumulator = class {
359
1931
  }
360
1932
  for (const { file, name } of this.missingDrawn) {
361
1933
  gaps.push(
362
- `${file} draws ${name}, but there is no ${name}.rb under ${path3.basename(this.drawDirectory)}/ beside it to read; whatever that file routes is missing from what suss reports`
1934
+ `${file} draws ${name}, but there is no ${name}.rb under ${path4.basename(this.drawDirectory)}/ beside it to read; whatever that file routes is missing from what suss reports`
363
1935
  );
364
1936
  }
365
1937
  return gaps;
@@ -396,14 +1968,16 @@ function handleResourceCall(call, ctx, out, plural) {
396
1968
  const only = readSymbolList(args.keyword.only);
397
1969
  const except = readSymbolList(args.keyword.except);
398
1970
  const table = plural ? RESTFUL_ROUTES_PLURAL : RESTFUL_ROUTES_SINGULAR;
399
- for (const [action, routeAt] of Object.entries(table)) {
1971
+ for (const [action, routesAt] of Object.entries(table)) {
400
1972
  if (only !== null && !only.includes(action)) {
401
1973
  continue;
402
1974
  }
403
1975
  if (except?.includes(action)) {
404
1976
  continue;
405
1977
  }
406
- out.add(controllerKey, action, routeAt(base));
1978
+ for (const route of routesAt(base)) {
1979
+ out.add(controllerKey, action, route);
1980
+ }
407
1981
  }
408
1982
  const nestedBase = plural ? `${base}/:${singularize(symbol)}_id` : base;
409
1983
  const nested = {
@@ -447,13 +2021,15 @@ function handleVerb(call, ctx, out, method) {
447
2021
  const on = wordValue(args.keyword.on, ctx);
448
2022
  const base = ctx.resource === void 0 ? ctx.pathPrefix : baseForOn(ctx.resource, on);
449
2023
  const target = readRouteTarget(args, ctx);
2024
+ const format = formatOf(args);
450
2025
  if (target !== null) {
451
2026
  const literalPath = args.positional[0] ? textValue(args.positional[0], ctx) : null;
452
- const path5 = target.path ?? literalPath;
453
- if (path5 !== null) {
2027
+ const path6 = target.path ?? literalPath;
2028
+ if (path6 !== null) {
454
2029
  out.add(joinKey(ctx.modulePrefix, target.controllerKey), target.action, {
455
2030
  method,
456
- path: joinPath(base, path5)
2031
+ path: joinPath(base, path6),
2032
+ ...format
457
2033
  });
458
2034
  }
459
2035
  return;
@@ -468,7 +2044,14 @@ function handleVerb(call, ctx, out, method) {
468
2044
  if (controllerKey === void 0) {
469
2045
  return;
470
2046
  }
471
- out.add(controllerKey, action, { method, path: joinPath(base, segment) });
2047
+ out.add(controllerKey, action, {
2048
+ method,
2049
+ path: joinPath(base, segment),
2050
+ ...format
2051
+ });
2052
+ }
2053
+ function formatOf(args) {
2054
+ return args.keyword.format?.type === "false" ? { formatless: true } : {};
472
2055
  }
473
2056
  function handleRoot(call, ctx, out) {
474
2057
  const args = readSimpleArgs(call, ctx.defaults);
@@ -598,9 +2181,9 @@ function handleMatch(call, ctx, out) {
598
2181
  }
599
2182
  }
600
2183
  function drawDirectoryOf(routesFile) {
601
- return path3.join(
602
- path3.dirname(routesFile),
603
- path3.basename(routesFile, path3.extname(routesFile))
2184
+ return path4.join(
2185
+ path4.dirname(routesFile),
2186
+ path4.basename(routesFile, path4.extname(routesFile))
604
2187
  );
605
2188
  }
606
2189
  function handleDraw(call, ctx, out) {
@@ -609,16 +2192,16 @@ function handleDraw(call, ctx, out) {
609
2192
  if (name === null) {
610
2193
  return;
611
2194
  }
612
- const file = path3.join(out.drawDirectory, `${name}.rb`);
2195
+ const file = path4.join(out.drawDirectory, `${name}.rb`);
613
2196
  if (out.drawn.has(file)) {
614
2197
  return;
615
2198
  }
616
2199
  out.drawn.add(file);
617
- if (!fs3.existsSync(file)) {
2200
+ if (!fs4.existsSync(file)) {
618
2201
  out.recordMissingDrawn(name);
619
2202
  return;
620
2203
  }
621
- const tree = parseRubySync2(fs3.readFileSync(file, "utf8"));
2204
+ const tree = parseRubySync2(fs4.readFileSync(file, "utf8"));
622
2205
  walkBody(tree.rootNode, ctx, out);
623
2206
  }
624
2207
  var HANDLERS = {
@@ -726,10 +2309,8 @@ function routeDeclarations(root, displayPath) {
726
2309
  found.push(declaration);
727
2310
  return;
728
2311
  }
729
- for (const child of node.namedChildren) {
730
- if (child !== null) {
731
- visit(child);
732
- }
2312
+ for (const child of children2(node)) {
2313
+ visit(child);
733
2314
  }
734
2315
  };
735
2316
  visit(root);
@@ -756,16 +2337,16 @@ function isApplication(owner) {
756
2337
  return owner === "Rails.application" || /(^|::)Application$/.test(owner);
757
2338
  }
758
2339
  function parseDeclarations(source) {
759
- if (!fs3.existsSync(source.file)) {
2340
+ if (!fs4.existsSync(source.file)) {
760
2341
  return [];
761
2342
  }
762
- const tree = parseRubySync2(fs3.readFileSync(source.file, "utf8"));
2343
+ const tree = parseRubySync2(fs4.readFileSync(source.file, "utf8"));
763
2344
  return routeDeclarations(tree.rootNode, source.displayPath);
764
2345
  }
765
2346
  function readRoutes(input) {
766
- if (!fs3.existsSync(input.routesFile.file)) {
2347
+ if (!fs4.existsSync(input.routesFile.file)) {
767
2348
  return {
768
- routeFor: () => null,
2349
+ routesFor: () => [],
769
2350
  fileFound: false,
770
2351
  gaps: [
771
2352
  `${input.routesFile.displayPath} does not exist, so this run assumes each action's path and method from Rails' RESTful naming convention instead of reading it from routing`
@@ -806,7 +2387,7 @@ function readRoutes(input) {
806
2387
  });
807
2388
  }
808
2389
  return {
809
- routeFor: (controllerKey, action) => out.routeFor(controllerKey, action),
2390
+ routesFor: (controllerKey, action) => out.routesFor(controllerKey, action),
810
2391
  fileFound: true,
811
2392
  gaps: out.gaps()
812
2393
  };
@@ -876,68 +2457,131 @@ var RACK_STATUS_CODE_NAMES = {
876
2457
  variant_also_negotiates: 506,
877
2458
  insufficient_storage: 507,
878
2459
  loop_detected: 508,
2460
+ bandwidth_limit_exceeded: 509,
879
2461
  not_extended: 510,
880
2462
  network_authentication_required: 511
881
2463
  };
882
- var optionsSchema = z.object({
2464
+ var STANDARD_ERROR = ["StandardError", "Exception"];
2465
+ var ACTION_CONTROLLER_ERROR = [
2466
+ "ActionController::ActionControllerError",
2467
+ ...STANDARD_ERROR
2468
+ ];
2469
+ var ACTIVE_RECORD_ERROR = [
2470
+ "ActiveRecord::ActiveRecordError",
2471
+ ...STANDARD_ERROR
2472
+ ];
2473
+ var INDEX_ERROR = ["IndexError", ...STANDARD_ERROR];
2474
+ var LIBRARY_EXCEPTIONS = {
2475
+ "ActionController::ActionControllerError": { ancestors: STANDARD_ERROR },
2476
+ "ActionController::BadRequest": {
2477
+ status: 400,
2478
+ ancestors: ACTION_CONTROLLER_ERROR
2479
+ },
2480
+ "ActionController::ParameterMissing": {
2481
+ status: 400,
2482
+ ancestors: ["KeyError", ...INDEX_ERROR]
2483
+ },
2484
+ "ActionController::UnpermittedParameters": { ancestors: INDEX_ERROR },
2485
+ "ActionController::RoutingError": {
2486
+ status: 404,
2487
+ ancestors: ACTION_CONTROLLER_ERROR
2488
+ },
2489
+ "AbstractController::ActionNotFound": {
2490
+ status: 404,
2491
+ ancestors: STANDARD_ERROR
2492
+ },
2493
+ "ActionController::MethodNotAllowed": {
2494
+ status: 405,
2495
+ ancestors: ACTION_CONTROLLER_ERROR
2496
+ },
2497
+ "ActionController::NotImplemented": {
2498
+ status: 501,
2499
+ ancestors: [
2500
+ "ActionController::MethodNotAllowed",
2501
+ ...ACTION_CONTROLLER_ERROR
2502
+ ]
2503
+ },
2504
+ "ActionController::UnknownFormat": {
2505
+ status: 406,
2506
+ ancestors: ACTION_CONTROLLER_ERROR
2507
+ },
2508
+ "ActionController::InvalidAuthenticityToken": {
2509
+ status: 422,
2510
+ ancestors: ACTION_CONTROLLER_ERROR
2511
+ },
2512
+ "ActiveRecord::ActiveRecordError": { ancestors: STANDARD_ERROR },
2513
+ "ActiveRecord::RecordNotFound": {
2514
+ status: 404,
2515
+ ancestors: ACTIVE_RECORD_ERROR
2516
+ },
2517
+ "ActiveRecord::RecordInvalid": {
2518
+ status: 422,
2519
+ ancestors: ACTIVE_RECORD_ERROR
2520
+ },
2521
+ "ActiveRecord::RecordNotSaved": {
2522
+ status: 422,
2523
+ ancestors: ACTIVE_RECORD_ERROR
2524
+ },
2525
+ "ActiveRecord::StaleObjectError": {
2526
+ status: 409,
2527
+ ancestors: ACTIVE_RECORD_ERROR
2528
+ }
2529
+ };
2530
+ var optionsSchema = z15.object({
883
2531
  /**
884
2532
  * The directory a bare controller name is looked up under, `app` in a
885
2533
  * scaffolded Rails app.
886
2534
  */
887
- root: z.string().optional(),
2535
+ root: z15.string().optional(),
888
2536
  /**
889
2537
  * The CLI fills this in with the config file's directory, or with the
890
2538
  * directory the run reads when there is no file. A relative `root` or
891
2539
  * `routesFile` resolves against it. A config file does not set it.
892
2540
  */
893
- configDirectory: z.string().optional(),
2541
+ configDirectory: z15.string().optional(),
894
2542
  /**
895
2543
  * Base classes beyond `ApplicationController` whose subclasses are
896
2544
  * controllers. A config file may not set this; a dependency stub
897
2545
  * fills it.
898
2546
  */
899
- baseClassNames: z.array(z.string()).optional(),
2547
+ baseClassNames: z15.array(z15.string()).optional(),
900
2548
  /**
901
2549
  * Controller methods a gem such as Devise adds, beyond the ones Rails
902
2550
  * defines. They are left off an action's effects the same way Rails'
903
2551
  * own are.
904
2552
  */
905
- inheritedMethodNames: z.array(z.string()).optional(),
2553
+ inheritedMethodNames: z15.array(z15.string()).optional(),
906
2554
  /**
907
2555
  * The routes file, `config/routes.rb` in a scaffolded app. A relative
908
2556
  * path resolves against `configDirectory` when there is one.
909
2557
  */
910
- routesFile: z.string().optional(),
2558
+ routesFile: z15.string().optional(),
911
2559
  /**
912
2560
  * Directories that each contain an engine, with its `Rails::Engine`
913
2561
  * class under `lib/` and its routes in `config/routes.rb`. A `*` in a
914
2562
  * segment matches any one directory, so `plugins/*` covers every
915
2563
  * plugin. Relative paths resolve against `configDirectory`.
916
2564
  */
917
- engineRoots: z.array(z.string()).optional(),
2565
+ engineRoots: z15.array(z15.string()).optional(),
918
2566
  /**
919
2567
  * Other files that add routes through a `Rails.application.routes.draw`,
920
2568
  * `.append` or `.prepend` block, most often a plugin's `plugin.rb`.
921
2569
  * They take `*` the same way `engineRoots` does, and relative paths
922
2570
  * resolve against `configDirectory`.
923
2571
  */
924
- routesFiles: z.array(z.string()).optional()
2572
+ routesFiles: z15.array(z15.string()).optional()
925
2573
  }).strict();
926
2574
  var RAILS_ROOT_CLASS_NAMES = [
927
2575
  "ActionController::Base",
928
2576
  "ActionController::API"
929
2577
  ];
930
- var RESTFUL_ACTIONS = {
931
- index: { method: "GET", pathTemplate: "/:resource" },
932
- show: { method: "GET", pathTemplate: "/:resource/:id" },
933
- new: { method: "GET", pathTemplate: "/:resource/new" },
934
- create: { method: "POST", pathTemplate: "/:resource" },
935
- edit: { method: "GET", pathTemplate: "/:resource/:id/edit" },
936
- update: { method: "PATCH", pathTemplate: "/:resource/:id" },
937
- destroy: { method: "DELETE", pathTemplate: "/:resource/:id" }
938
- };
939
2578
  var RESPONSE_STATUS_CALLS = [
940
- { name: "render", statusKeyword: "status" },
2579
+ {
2580
+ name: "render",
2581
+ statusKeyword: "status",
2582
+ bodyKeyword: "json",
2583
+ bodySerializers: ["to_json", "as_json"]
2584
+ },
941
2585
  { name: "head", statusArgument: 0, statusKeyword: "status" },
942
2586
  { name: "redirect_to", statusKeyword: "status", defaultStatusCode: 302 },
943
2587
  { name: "redirect_back", statusKeyword: "status", defaultStatusCode: 302 }
@@ -993,22 +2637,15 @@ function controllerKeyFromQualified(qualifiedName, acronyms) {
993
2637
  const withoutSuffix = qualifiedName.endsWith("Controller") ? qualifiedName.slice(0, -"Controller".length) : qualifiedName;
994
2638
  return underscoreConstantPath2(withoutSuffix, acronyms);
995
2639
  }
996
- function conventionalRoute(controllerKey, actionName) {
997
- const template = RESTFUL_ACTIONS[actionName];
998
- if (template === void 0) {
999
- return null;
1000
- }
2640
+ function conventionalRoutes(controllerKey, actionName) {
1001
2641
  const resource = controllerKey.split("/").pop() ?? controllerKey;
1002
- return {
1003
- method: template.method,
1004
- path: template.pathTemplate.replace(":resource", resource)
1005
- };
2642
+ return restfulRoutes(actionName, `/${resource}`);
1006
2643
  }
1007
2644
  function resolveAgainst(configDirectory, value) {
1008
- if (configDirectory === void 0 || path4.isAbsolute(value)) {
2645
+ if (configDirectory === void 0 || path5.isAbsolute(value)) {
1009
2646
  return value;
1010
2647
  }
1011
- return path4.resolve(configDirectory, value);
2648
+ return path5.resolve(configDirectory, value);
1012
2649
  }
1013
2650
  function railsFramework(options = {}) {
1014
2651
  const root = resolveAgainst(options.configDirectory, options.root ?? "app");
@@ -1016,7 +2653,8 @@ function railsFramework(options = {}) {
1016
2653
  options.configDirectory,
1017
2654
  options.routesFile ?? "config/routes.rb"
1018
2655
  );
1019
- const displayPathOf = (file) => options.configDirectory === void 0 ? file : path4.relative(options.configDirectory, file);
2656
+ const railsRoot = path5.dirname(root);
2657
+ const displayPathOf = (file) => path5.isAbsolute(file) ? path5.relative(railsRoot, file) : file;
1020
2658
  const engineRoots = () => expandPatterns(options.configDirectory, options.engineRoots);
1021
2659
  const extraRoutesFiles = () => expandPatterns(options.configDirectory, options.routesFiles);
1022
2660
  const inflections = options.configDirectory === void 0 ? EMPTY_INFLECTIONS : readInflections(options.configDirectory);
@@ -1024,7 +2662,7 @@ function railsFramework(options = {}) {
1024
2662
  const routesInput = () => ({
1025
2663
  routesFile: {
1026
2664
  file: routesFile,
1027
- displayPath: options.routesFile ?? "config/routes.rb"
2665
+ displayPath: displayPathOf(routesFile)
1028
2666
  },
1029
2667
  engines: readEngines(engineRoots(), acronyms).map((engine) => ({
1030
2668
  ...engine,
@@ -1047,24 +2685,28 @@ function railsFramework(options = {}) {
1047
2685
  ...options.baseClassNames ?? []
1048
2686
  ],
1049
2687
  root,
2688
+ autoloadRoots: options.configDirectory === void 0 ? [] : readAutoloadRoots(options.configDirectory),
1050
2689
  pathConvention: "railsUnderscore",
1051
2690
  acronyms,
1052
2691
  ancestryRootClassNames: [...RAILS_ROOT_CLASS_NAMES],
1053
2692
  defaultStatusCode: 200,
1054
2693
  responseStatusCalls: RESPONSE_STATUS_CALLS,
1055
2694
  statusCodeNames: RACK_STATUS_CODE_NAMES,
2695
+ formatSwitch: { name: "respond_to", matchesEvery: "any" },
2696
+ libraryExceptions: LIBRARY_EXCEPTIONS,
1056
2697
  inheritedMethodNames: [
1057
2698
  ...RAILS_CONTROLLER_METHODS,
1058
2699
  ...options.inheritedMethodNames ?? []
1059
2700
  ],
1060
2701
  routesFile,
1061
2702
  filters: CONTROLLER_FILTERS,
1062
- routeFor: (controllerQualifiedName, actionName) => {
2703
+ routesFor: (controllerQualifiedName, actionName) => {
1063
2704
  const key = controllerKeyFromQualified(controllerQualifiedName, acronyms);
1064
2705
  const found = routeTable();
1065
- return found.fileFound ? found.routeFor(key, actionName) : conventionalRoute(key, actionName);
2706
+ return found.fileFound ? found.routesFor(key, actionName) : conventionalRoutes(key, actionName);
1066
2707
  },
1067
- routingGaps: () => routeTable().gaps
2708
+ routingGaps: () => routeTable().gaps,
2709
+ optionalFormat: true
1068
2710
  };
1069
2711
  return {
1070
2712
  name: "rails",
@@ -1089,7 +2731,10 @@ function railsFramework(options = {}) {
1089
2731
  ...drawableRoutesFiles(routesFile),
1090
2732
  ...engineRoots().flatMap(engineSourceFiles),
1091
2733
  ...extraRoutesFiles(),
1092
- ...options.configDirectory === void 0 ? [] : inflectionFiles(options.configDirectory)
2734
+ ...options.configDirectory === void 0 ? [] : [
2735
+ ...inflectionFiles(options.configDirectory),
2736
+ ...applicationFile(options.configDirectory)
2737
+ ]
1093
2738
  ]
1094
2739
  };
1095
2740
  }
@@ -1100,10 +2745,10 @@ function expandPatterns(configDirectory, patterns) {
1100
2745
  }
1101
2746
  function drawableRoutesFiles(routesFile) {
1102
2747
  const directory = drawDirectoryOf(routesFile);
1103
- if (!fs4.existsSync(directory)) {
2748
+ if (!fs5.existsSync(directory)) {
1104
2749
  return [];
1105
2750
  }
1106
- return fs4.readdirSync(directory).filter((name) => name.endsWith(".rb")).sort().map((name) => path4.join(directory, name));
2751
+ return fs5.readdirSync(directory).filter((name) => name.endsWith(".rb")).sort().map((name) => path5.join(directory, name));
1107
2752
  }
1108
2753
  var declares = {
1109
2754
  kind: "framework",