@suss/packs 0.35.1 → 0.36.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/node.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  } from "ts-morph";
7
7
  import {
8
8
  declarationsBehind,
9
- findEnclosingFunction,
9
+ dottedPathOf,
10
10
  functionTargetOf,
11
11
  isDefaultedAt,
12
12
  stringValueOf,
@@ -36,6 +36,7 @@ import { functionCallBinding } from "@suss/behavioral-ir";
36
36
  import { Node as Node4 } from "ts-morph";
37
37
  import { writtenPayloadOf } from "@suss/adapter-typescript";
38
38
  import { ioBinding } from "@suss/behavioral-ir";
39
+ import { callSiteKey } from "@suss/extractor";
39
40
  var RECOGNITION = "@suss/runtime-node";
40
41
  function configBinding(where) {
41
42
  return runtimeConfigBinding({ recognition: RECOGNITION, ...where });
@@ -295,98 +296,46 @@ function schemaEnvReadsInside(callee, resolution) {
295
296
  SCHEMA_READS_INSIDE.set(callee, found);
296
297
  return found;
297
298
  }
298
- var readName = (name) => `process.env.${name}`;
299
- function isProcessEnv(node) {
299
+ var ENVIRONMENT_OBJECTS = [
300
+ "process.env",
301
+ "import.meta.env"
302
+ ];
303
+ var PROCESS_ENV = "process.env";
304
+ var IMPORT_META_ENV = "import.meta.env";
305
+ var VITE_BUILD_KEYS = /* @__PURE__ */ new Set(["MODE", "BASE_URL", "PROD", "DEV", "SSR"]);
306
+ var readName = (read) => read.name === null ? read.node.getText() : `${read.object}.${read.name}`;
307
+ function environmentSpelling(node) {
300
308
  if (!N2.isPropertyAccessExpression(node) || node.getName() !== "env") {
301
- return false;
309
+ return null;
302
310
  }
303
- const root = node.getExpression();
304
- return N2.isIdentifier(root) && root.getText() === "process";
311
+ const path = dottedPathOf(node);
312
+ return path !== null && ENVIRONMENT_OBJECTS.includes(path) ? path : null;
305
313
  }
306
314
  function dottedRead(node) {
307
- if (!isProcessEnv(node.getExpression())) {
315
+ const object = environmentSpelling(node.getExpression());
316
+ if (object === null) {
308
317
  return [];
309
318
  }
310
- return [{ name: node.getName(), defaulted: isDefaultedAt(node), node }];
319
+ return [
320
+ { name: node.getName(), defaulted: isDefaultedAt(node), node, object }
321
+ ];
311
322
  }
312
- function bracketRead(access, resolution) {
323
+ function bracketRead(access, object) {
313
324
  const argument = access.getArgumentExpression();
314
325
  if (argument === void 0) {
315
326
  return [];
316
327
  }
317
- if (N2.isStringLiteral(argument) || N2.isNoSubstitutionTemplateLiteral(argument)) {
318
- const name = argument.getLiteralValue();
319
- if (name.length === 0) {
320
- return [];
321
- }
322
- return [{ name, defaulted: isDefaultedAt(access), node: access }];
323
- }
324
- if (N2.isIdentifier(argument)) {
325
- return readsThroughParameter(access, argument, resolution);
326
- }
327
- return [];
328
- }
329
- var CALLER_LOOKUPS = /* @__PURE__ */ new WeakMap();
330
- function readsThroughParameter(access, index, resolution) {
331
- const remembered = CALLER_LOOKUPS.get(access);
332
- if (remembered !== void 0) {
333
- return remembered;
334
- }
335
- const found = callerLiteralReads(access, index, resolution);
336
- CALLER_LOOKUPS.set(access, found);
337
- return found;
338
- }
339
- function callerLiteralReads(access, index, resolution) {
340
- if (resolution === void 0) {
341
- return [];
342
- }
343
- const enclosing = findEnclosingFunction(access);
344
- if (enclosing === null) {
345
- return [];
346
- }
347
- const start = enclosing.getParameters().find((parameter) => parameter.getName() === index.getText());
348
- if (start === void 0) {
349
- return [];
350
- }
351
328
  const defaulted = isDefaultedAt(access);
352
- const reads = [];
353
- const pending = [start];
354
- const taken = /* @__PURE__ */ new Set();
355
- while (pending.length > 0) {
356
- const wanted = pending.pop();
357
- if (taken.has(wanted)) {
358
- continue;
359
- }
360
- taken.add(wanted);
361
- for (const passed of resolution.argumentsPassedTo(wanted)) {
362
- const literal = stringValueOf(passed.argument, resolution);
363
- if (literal !== null && literal.length > 0) {
364
- reads.push({ name: literal, defaulted, node: passed.call });
365
- continue;
366
- }
367
- const forwarded = forwardedParameter(passed.argument, passed.call);
368
- if (forwarded !== null) {
369
- pending.push(forwarded);
370
- }
371
- }
372
- }
373
- return reads;
374
- }
375
- function forwardedParameter(passed, call) {
376
- if (!N2.isIdentifier(passed)) {
377
- return null;
378
- }
379
- const caller = findEnclosingFunction(call);
380
- if (caller === null) {
381
- return null;
329
+ if (!N2.isStringLiteral(argument) && !N2.isNoSubstitutionTemplateLiteral(argument)) {
330
+ return [{ name: null, defaulted, node: access, object }];
382
331
  }
383
- const behind = symbolBehind(passed)?.getValueDeclaration();
384
- if (behind === void 0 || !N2.isParameterDeclaration(behind)) {
385
- return null;
332
+ const name = argument.getLiteralValue();
333
+ if (name.length === 0) {
334
+ return [];
386
335
  }
387
- return behind.getParent() === caller ? behind : null;
336
+ return [{ name, defaulted, node: access, object }];
388
337
  }
389
- function bindingRead(element) {
338
+ function bindingRead(element, object) {
390
339
  if (element.getDotDotDotToken() !== void 0) {
391
340
  return [];
392
341
  }
@@ -404,24 +353,25 @@ function bindingRead(element) {
404
353
  // A binding default covers a missing variable the same way `??`
405
354
  // does after a dotted read.
406
355
  defaulted: element.getInitializer() !== void 0 || isDefaultedAt(element),
407
- node: element
356
+ node: element,
357
+ object
408
358
  }
409
359
  ];
410
360
  }
411
- function destructuredReads(declaration) {
361
+ function destructuredReads(declaration, object) {
412
362
  const pattern = declaration.getNameNode();
413
363
  if (!N2.isObjectBindingPattern(pattern)) {
414
364
  return [];
415
365
  }
416
- return pattern.getElements().flatMap(bindingRead);
366
+ return pattern.getElements().flatMap((element) => bindingRead(element, object));
417
367
  }
418
- function readsThroughEnvObject(envNode, resolution) {
368
+ function readsThroughEnvObject(envNode, object) {
419
369
  const parent = envNode.getParent();
420
370
  if (N2.isElementAccessExpression(parent)) {
421
- return bracketRead(parent, resolution);
371
+ return bracketRead(parent, object);
422
372
  }
423
373
  if (N2.isVariableDeclaration(parent)) {
424
- return destructuredReads(parent);
374
+ return destructuredReads(parent, object);
425
375
  }
426
376
  return [];
427
377
  }
@@ -433,7 +383,8 @@ function envReadsAt(node, resolution) {
433
383
  if (!N2.isPropertyAccessExpression(node)) {
434
384
  return [];
435
385
  }
436
- return isProcessEnv(node) ? readsThroughEnvObject(node, resolution) : dottedRead(node);
386
+ const object = environmentSpelling(node);
387
+ return object === null ? dottedRead(node) : readsThroughEnvObject(node, object);
437
388
  }
438
389
  function readsThroughHelperCall(call, resolution) {
439
390
  const callee = functionBehindCallee(call.getExpression(), resolution);
@@ -442,14 +393,15 @@ function readsThroughHelperCall(call, resolution) {
442
393
  }
443
394
  const sitesNaming = namesReadAtSites(callee, resolution);
444
395
  const parameters = callee.getParameters();
445
- const defaultedByName = /* @__PURE__ */ new Map();
396
+ const byName = /* @__PURE__ */ new Map();
446
397
  call.getArguments().forEach((passed, at) => {
447
398
  const parameter = parameters[at];
448
399
  if (parameter === void 0) {
449
400
  return;
450
401
  }
451
402
  const sites = sitesNaming(parameter) ?? directEnvReads(callee, parameter);
452
- if (sites.length === 0) {
403
+ const [first] = sites;
404
+ if (first === void 0 || handsOnParameter(passed)) {
453
405
  return;
454
406
  }
455
407
  const literal = stringValueOf(passed, resolution);
@@ -457,25 +409,40 @@ function readsThroughHelperCall(call, resolution) {
457
409
  return;
458
410
  }
459
411
  const everywhere = sites.every(isDefaultedAt);
460
- defaultedByName.set(
461
- literal,
462
- (defaultedByName.get(literal) ?? true) && everywhere
463
- );
412
+ const known = byName.get(literal);
413
+ byName.set(literal, {
414
+ defaulted: (known?.defaulted ?? true) && everywhere,
415
+ object: known?.object ?? objectReadAt(first)
416
+ });
464
417
  });
465
418
  const wrapped = isDefaultedAt(call);
466
419
  return [
467
- ...[...defaultedByName].map(([name, atSites]) => ({
420
+ ...[...byName].map(([name, read]) => ({
468
421
  name,
469
- defaulted: atSites || wrapped,
470
- node: call
422
+ defaulted: read.defaulted || wrapped,
423
+ node: call,
424
+ object: read.object
471
425
  })),
472
426
  // A config module often parses its schema inside a function that
473
427
  // every handler calls, so each call reports the keys that parse reads.
474
428
  ...anchoredAt(schemaEnvReadsInside(callee, resolution), call)
475
429
  ];
476
430
  }
431
+ function handsOnParameter(passed) {
432
+ if (!N2.isIdentifier(passed)) {
433
+ return false;
434
+ }
435
+ const declared = symbolBehind(passed)?.getValueDeclaration();
436
+ return declared !== void 0 && N2.isParameterDeclaration(declared);
437
+ }
438
+ function objectReadAt(site) {
439
+ if (!N2.isElementAccessExpression(site)) {
440
+ return PROCESS_ENV;
441
+ }
442
+ return environmentSpelling(site.getExpression()) ?? PROCESS_ENV;
443
+ }
477
444
  function anchoredAt(reads, node) {
478
- return reads.map((read) => ({ ...read, node }));
445
+ return reads.map((read) => ({ ...read, node, object: PROCESS_ENV }));
479
446
  }
480
447
  function functionBehindCallee(callee, resolution) {
481
448
  const nameNode = N2.isPropertyAccessExpression(callee) ? callee.getNameNode() : callee;
@@ -524,7 +491,7 @@ function directEnvRead(fn, parameter) {
524
491
  for (const access of fn.getDescendantsOfKind(
525
492
  SyntaxKind2.ElementAccessExpression
526
493
  )) {
527
- if (!isProcessEnv(access.getExpression())) {
494
+ if (environmentSpelling(access.getExpression()) === null) {
528
495
  continue;
529
496
  }
530
497
  const argument = access.getArgumentExpression();
@@ -534,11 +501,17 @@ function directEnvRead(fn, parameter) {
534
501
  }
535
502
  return null;
536
503
  }
504
+ function readsBuildKey(read) {
505
+ return read.object === IMPORT_META_ENV && read.name !== null && VITE_BUILD_KEYS.has(read.name);
506
+ }
537
507
  function configReadEffect(read, where) {
508
+ if (readsBuildKey(read)) {
509
+ return opaqueRuntimeRead(readName(read));
510
+ }
538
511
  return {
539
512
  type: "interaction",
540
513
  binding: configBinding(where),
541
- callee: readName(read.name),
514
+ callee: readName(read),
542
515
  interaction: {
543
516
  class: "config-read",
544
517
  name: read.name,
@@ -561,6 +534,7 @@ function envVarRecognizer(opts = {}) {
561
534
  );
562
535
  }
563
536
  var FILE_LOCATION_GLOBALS = /* @__PURE__ */ new Set(["__dirname", "__filename"]);
537
+ var ENVIRONMENT_PROPERTY = "env";
564
538
  var importMetaRecognizer = (access, _ctx) => {
565
539
  const node = access;
566
540
  if (!Node.isPropertyAccessExpression(node)) {
@@ -574,6 +548,9 @@ var importMetaRecognizer = (access, _ctx) => {
574
548
  if (keyword === void 0 || inner.getNameNode().getText() !== "meta") {
575
549
  return null;
576
550
  }
551
+ if (node.getName() === ENVIRONMENT_PROPERTY) {
552
+ return null;
553
+ }
577
554
  return [opaqueRuntimeRead(node.getText())];
578
555
  };
579
556
  var fileLocationRecognizer = (access, _ctx) => {
@@ -868,14 +845,13 @@ var streamWriteRecognizer = (call, ctx) => {
868
845
  type: "interaction",
869
846
  binding: ioBinding({ recognition: RECOGNITION2, target }),
870
847
  callee: callee.getText(),
871
- groupId: callSiteId(written),
872
848
  interaction: {
873
849
  class: "stream-write",
874
850
  payload: payload.shape,
875
851
  serialized: payload.serialized
876
852
  }
877
853
  };
878
- return [effect];
854
+ return [{ ...effect, groupId: callSiteKey(effect) }];
879
855
  };
880
856
  function targetOf(receiver, method, resolution) {
881
857
  const consoleStream = Object.hasOwn(CONSOLE_STREAMS, method) ? CONSOLE_STREAMS[method] : void 0;
@@ -904,11 +880,7 @@ function viaParameter(receiver, annotations, resolution) {
904
880
  function streamName(path) {
905
881
  return path.slice(path.lastIndexOf(".") + 1);
906
882
  }
907
- function callSiteId(call) {
908
- const { line, column } = call.getSourceFile().getLineAndColumnAtPos(call.getStart());
909
- return `${line}:${column}`;
910
- }
911
- var PACK_VERSION = "0.3.0";
883
+ var PACK_VERSION = "0.4.0";
912
884
  var optionsSchema = z.object({
913
885
  /**
914
886
  * The kind of deployment that runtime-config reads such as
@@ -955,7 +927,7 @@ function nodeRuntimePack(options = {}) {
955
927
  argumentParsers: PARSE_ARGS,
956
928
  inputMapping: { type: "positionalParams", params: [] },
957
929
  invocationRecognizers: [schedulingRecognizer, streamWriteRecognizer],
958
- environmentObjects: ["process.env"],
930
+ environmentObjects: [...ENVIRONMENT_OBJECTS],
959
931
  streamObjects: PROCESS_STREAMS,
960
932
  accessRecognizers: [
961
933
  // envRecognizer handles `process.env.X` and processRecognizer skips
@@ -973,7 +945,7 @@ var declares = {
973
945
  package: "@suss/runtime-node",
974
946
  dependencies: [],
975
947
  shippedWith: "typescript",
976
- reads: "Node.js runtime primitives, scheduling and the `process` surface, each recorded as an interaction effect. A `process.env.X` read, or a key of a schema parsed against `process.env`, becomes a config-read interaction. A module-loading global becomes a metadata-read interaction. A `console` call or a write to `process.stdout` or `process.stderr` becomes a stream-write interaction on the stream it goes to."
948
+ reads: "Node.js runtime primitives, scheduling and the `process` surface, each recorded as an interaction effect. A `process.env.X` or `import.meta.env.X` read, or a key of a schema parsed against either, becomes a config-read interaction. A module-loading global becomes a metadata-read interaction. A `console` call or a write to `process.stdout` or `process.stderr` becomes a stream-write interaction on the stream it goes to."
977
949
  };
978
950
  var index_default = nodeRuntimePack;
979
951
  export {
package/dist/rails.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  symbolValue
30
30
  } from "@suss/adapter-ruby";
31
31
 
32
- // ../ir-core/dist/chunk-A46EUIKC.js
32
+ // ../ir-core/dist/chunk-7IOGU4OC.js
33
33
  import { z as z13 } from "zod";
34
34
  import { z as z12 } from "zod";
35
35
  import { z } from "zod";
@@ -1309,7 +1309,21 @@ var SourceLocationSchema = z13.object({
1309
1309
  * project's `suss.json` gives it. Set only when the project lists its
1310
1310
  * modules, and then on every unit whose file is under a module's root.
1311
1311
  */
1312
- module: z13.string().optional()
1312
+ module: z13.string().optional(),
1313
+ /**
1314
+ * The git repository the extract ran in. Summaries from several
1315
+ * repositories can share a folder, and two of them can both have
1316
+ * `src/index.ts::handler`; this says which one each came from, and
1317
+ * which commit, so a link opens the file that was read. `path` is
1318
+ * where `file` is measured from, relative to the repository's top.
1319
+ * Absent outside a repository.
1320
+ */
1321
+ repository: z13.object({
1322
+ name: z13.string(),
1323
+ path: z13.string(),
1324
+ remote: z13.string().optional(),
1325
+ commit: z13.string().optional()
1326
+ }).optional()
1313
1327
  });
1314
1328
  var BoundaryBindingSchema = z13.object({
1315
1329
  transport: z13.string(),
@@ -2657,7 +2671,8 @@ function railsFramework(options = {}) {
2657
2671
  const displayPathOf = (file) => path5.isAbsolute(file) ? path5.relative(railsRoot, file) : file;
2658
2672
  const engineRoots = () => expandPatterns(options.configDirectory, options.engineRoots);
2659
2673
  const extraRoutesFiles = () => expandPatterns(options.configDirectory, options.routesFiles);
2660
- const inflections = options.configDirectory === void 0 ? EMPTY_INFLECTIONS : readInflections(options.configDirectory);
2674
+ const appRoot = options.configDirectory === void 0 ? void 0 : railsRoot;
2675
+ const inflections = appRoot === void 0 ? EMPTY_INFLECTIONS : readInflections(appRoot);
2661
2676
  const acronyms = inflections.acronyms;
2662
2677
  const routesInput = () => ({
2663
2678
  routesFile: {
@@ -2685,7 +2700,7 @@ function railsFramework(options = {}) {
2685
2700
  ...options.baseClassNames ?? []
2686
2701
  ],
2687
2702
  root,
2688
- autoloadRoots: options.configDirectory === void 0 ? [] : readAutoloadRoots(options.configDirectory),
2703
+ autoloadRoots: appRoot === void 0 ? [] : readAutoloadRoots(appRoot),
2689
2704
  pathConvention: "railsUnderscore",
2690
2705
  acronyms,
2691
2706
  ancestryRootClassNames: [...RAILS_ROOT_CLASS_NAMES],
@@ -2723,6 +2738,7 @@ function railsFramework(options = {}) {
2723
2738
  headers: { path: ["request", "headers"], saysWhichField: true },
2724
2739
  params: { path: ["params"], saysWhichField: true }
2725
2740
  },
2741
+ bootTemplates: () => configTemplates(railsRoot),
2726
2742
  // These files change an action's binding without being walked, so the
2727
2743
  // cache key has to list them. This runs before the grammar loads, so
2728
2744
  // nothing here parses Ruby.
@@ -2731,10 +2747,7 @@ function railsFramework(options = {}) {
2731
2747
  ...drawableRoutesFiles(routesFile),
2732
2748
  ...engineRoots().flatMap(engineSourceFiles),
2733
2749
  ...extraRoutesFiles(),
2734
- ...options.configDirectory === void 0 ? [] : [
2735
- ...inflectionFiles(options.configDirectory),
2736
- ...applicationFile(options.configDirectory)
2737
- ]
2750
+ ...appRoot === void 0 ? [] : [...inflectionFiles(appRoot), ...applicationFile(appRoot)]
2738
2751
  ]
2739
2752
  };
2740
2753
  }
@@ -2743,6 +2756,13 @@ function expandPatterns(configDirectory, patterns) {
2743
2756
  (pattern) => expandPathPattern(resolveAgainst(configDirectory, pattern))
2744
2757
  );
2745
2758
  }
2759
+ function configTemplates(railsRoot) {
2760
+ const directory = path5.join(railsRoot, "config");
2761
+ if (!fs5.existsSync(directory)) {
2762
+ return [];
2763
+ }
2764
+ return fs5.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".yml")).map((entry) => path5.join(directory, entry.name)).sort();
2765
+ }
2746
2766
  function drawableRoutesFiles(routesFile) {
2747
2767
  const directory = drawDirectoryOf(routesFile);
2748
2768
  if (!fs5.existsSync(directory)) {
@@ -2759,7 +2779,7 @@ var declares = {
2759
2779
  { ecosystem: "rubygems", name: "rails" },
2760
2780
  { ecosystem: "rubygems", name: "railties" }
2761
2781
  ],
2762
- reads: "Rails controller actions (Ruby), bound to the method and path `config/routes.rb` gives each one. An action the routes file does not reach is still discovered, with no boundary.",
2782
+ reads: "Rails controller actions (Ruby), bound to the method and path `config/routes.rb` gives each one. An action the routes file does not reach is still discovered, with no boundary. The YAML files under `config/` that Rails renders through ERB at boot are read as code that runs when the app loads.",
2763
2783
  configuration: {
2764
2784
  file: "suss.rails.json",
2765
2785
  example: {
@@ -2770,7 +2790,8 @@ var declares = {
2770
2790
  },
2771
2791
  required: false,
2772
2792
  why: "the app directory a controller is defined under and the routes file suss reads each action's method and path from; rails new scaffolds both at these paths. engineRoots lists where the project keeps engines it mounts, and routesFiles any other file that adds routes, when it has either.",
2773
- defaults: { root: "app", routesFile: "config/routes.rb" }
2793
+ defaults: { root: "app", routesFile: "config/routes.rb" },
2794
+ paths: ["root", "routesFile", "engineRoots", "routesFiles"]
2774
2795
  }
2775
2796
  };
2776
2797
  var index_default = railsFramework;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/packs",
3
- "version": "0.35.1",
3
+ "version": "0.36.0",
4
4
  "type": "module",
5
5
  "description": "Every pack suss ships in one install, so it can read Express, Hono, NestJS, FastAPI, Rails, Prisma, OpenAPI and the rest.",
6
6
  "keywords": [
@@ -49,6 +49,14 @@
49
49
  "types": "./dist/aws-lambda.d.ts",
50
50
  "import": "./dist/aws-lambda.js"
51
51
  },
52
+ "./aws-lambda-python": {
53
+ "types": "./dist/aws-lambda-python.d.ts",
54
+ "import": "./dist/aws-lambda-python.js"
55
+ },
56
+ "./aws-lambda-ruby": {
57
+ "types": "./dist/aws-lambda-ruby.d.ts",
58
+ "import": "./dist/aws-lambda-ruby.js"
59
+ },
52
60
  "./aws-s3": {
53
61
  "types": "./dist/aws-s3.d.ts",
54
62
  "import": "./dist/aws-s3.js"
@@ -267,15 +275,16 @@
267
275
  },
268
276
  "dependencies": {
269
277
  "@mrleebo/prisma-ast": "^0.15.0",
270
- "@suss/adapter-python": "0.35.1",
271
- "@suss/adapter-ruby": "0.35.1",
272
- "@suss/adapter-typescript": "0.35.1",
273
- "@suss/behavioral-ir": "0.35.1",
274
- "@suss/contract-graphql": "0.35.1",
275
- "@suss/extractor": "0.35.1",
276
- "@suss/manifest-aws": "0.35.1",
277
- "@suss/recognize": "0.35.1",
278
- "@suss/sql": "0.35.1",
278
+ "@suss/adapter-python": "0.36.0",
279
+ "@suss/adapter-ruby": "0.36.0",
280
+ "@suss/adapter-typescript": "0.36.0",
281
+ "@suss/behavioral-ir": "0.36.0",
282
+ "@suss/contract-graphql": "0.36.0",
283
+ "@suss/contract-serverless": "0.36.0",
284
+ "@suss/extractor": "0.36.0",
285
+ "@suss/manifest-aws": "0.36.0",
286
+ "@suss/recognize": "0.36.0",
287
+ "@suss/sql": "0.36.0",
279
288
  "zod": "^4.0.0"
280
289
  },
281
290
  "devDependencies": {