@amerged/ohmyhost-cli 0.1.37 → 0.1.38

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @amerged/ohmyhost-cli
2
2
 
3
- ohmyho.st cli client, version 0.1.37.
3
+ ohmyho.st cli client, version 0.1.38.
4
4
 
5
5
  ```sh
6
6
  npm install --global @amerged/ohmyhost-cli
@@ -802,7 +802,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
802
802
  SOFTWARE.
803
803
 
804
804
 
805
- ## fast-uri 3.1.8
805
+ ## fast-uri 3.1.9
806
806
 
807
807
  ### LICENSE
808
808
 
package/dist/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire as __ohmyhostCreateRequire } from "node:module"; const require = __ohmyhostCreateRequire(import.meta.url);
3
3
  import {
4
4
  runProductionCli
5
- } from "./chunk-IHXNADOQ.js";
5
+ } from "./chunk-EWKJGJQU.js";
6
6
  import "./chunk-7D2RFLWM.js";
7
7
 
8
8
  // apps/product-cli/dist/bin.js
@@ -22796,9 +22796,9 @@ var require_data = __commonJS({
22796
22796
  }
22797
22797
  });
22798
22798
 
22799
- // node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js
22799
+ // node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js
22800
22800
  var require_utils = __commonJS({
22801
- "node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js"(exports, module) {
22801
+ "node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js"(exports, module) {
22802
22802
  "use strict";
22803
22803
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
22804
22804
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
@@ -23030,6 +23030,37 @@ var require_utils = __commonJS({
23030
23030
  }
23031
23031
  return output.join("");
23032
23032
  }
23033
+ function removeDotSegmentsRelative(path2) {
23034
+ if (path2 === "" || path2[0] === "/") {
23035
+ return removeDotSegments(path2);
23036
+ }
23037
+ const segments = path2.split("/");
23038
+ const last = segments.length - 1;
23039
+ const output = [];
23040
+ let trailingDirectory = false;
23041
+ for (let i = 0; i <= last; i++) {
23042
+ const segment = segments[i];
23043
+ if (segment === "." || segment === "..") {
23044
+ if (segment === "..") {
23045
+ if (output.length !== 0 && output[output.length - 1] !== "..") {
23046
+ output.pop();
23047
+ } else {
23048
+ output.push("..");
23049
+ }
23050
+ }
23051
+ trailingDirectory = i === last;
23052
+ } else {
23053
+ output.push(segment);
23054
+ }
23055
+ }
23056
+ if (trailingDirectory && output[output.length - 1] !== "..") {
23057
+ output.push("");
23058
+ }
23059
+ if (output.length === 0 || output[0] === "" || output[0].includes(":")) {
23060
+ output.unshift(".");
23061
+ }
23062
+ return output.join("/");
23063
+ }
23033
23064
  var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
23034
23065
  var HOST_DELIM_RE = /[@/?#:]/g;
23035
23066
  var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
@@ -23290,6 +23321,7 @@ var require_utils = __commonJS({
23290
23321
  encodeFragment,
23291
23322
  escapePreservingEscapes,
23292
23323
  removeDotSegments,
23324
+ removeDotSegmentsRelative,
23293
23325
  isIPv4,
23294
23326
  isUUID,
23295
23327
  normalizeIPv6,
@@ -23298,9 +23330,9 @@ var require_utils = __commonJS({
23298
23330
  }
23299
23331
  });
23300
23332
 
23301
- // node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js
23333
+ // node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js
23302
23334
  var require_schemes = __commonJS({
23303
- "node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js"(exports, module) {
23335
+ "node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js"(exports, module) {
23304
23336
  "use strict";
23305
23337
  var { isUUID } = require_utils();
23306
23338
  var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
@@ -23509,20 +23541,22 @@ var require_schemes = __commonJS({
23509
23541
  }
23510
23542
  });
23511
23543
 
23512
- // node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js
23544
+ // node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js
23513
23545
  var require_fast_uri = __commonJS({
23514
- "node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js"(exports, module) {
23546
+ "node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js"(exports, module) {
23515
23547
  "use strict";
23516
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
23548
+ var { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
23517
23549
  var { SCHEMES, getSchemeHandler } = require_schemes();
23518
23550
  var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
23519
23551
  var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
23520
- function decodeValidScheme(scheme) {
23521
- const decodedScheme = unescape(String(scheme));
23522
- if (!VALID_SCHEME.test(decodedScheme)) {
23552
+ var MALFORMED_HOST_ERROR = "URI host is malformed.";
23553
+ var FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
23554
+ function validateScheme(scheme) {
23555
+ const stringScheme = String(scheme);
23556
+ if (!VALID_SCHEME.test(stringScheme)) {
23523
23557
  throw new TypeError(MALFORMED_SCHEME_ERROR);
23524
23558
  }
23525
- return decodedScheme;
23559
+ return stringScheme;
23526
23560
  }
23527
23561
  function normalize(uri, options) {
23528
23562
  if (typeof uri === "string") {
@@ -23559,6 +23593,9 @@ var require_fast_uri = __commonJS({
23559
23593
  const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
23560
23594
  const resolvedHost = resolved.host;
23561
23595
  const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
23596
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
23597
+ throw new Error(MALFORMED_HOST_ERROR);
23598
+ }
23562
23599
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
23563
23600
  const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
23564
23601
  if (resolved.error && !encodedASCIIHost) {
@@ -23600,14 +23637,15 @@ var require_fast_uri = __commonJS({
23600
23637
  if (relative2.path[0] === "/") {
23601
23638
  target.path = removeDotSegments(relative2.path);
23602
23639
  } else {
23603
- if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
23640
+ const baseHasAuthority = base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0;
23641
+ if (baseHasAuthority && !base.path) {
23604
23642
  target.path = "/" + relative2.path;
23605
23643
  } else if (!base.path) {
23606
23644
  target.path = relative2.path;
23607
23645
  } else {
23608
23646
  target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
23609
23647
  }
23610
- target.path = removeDotSegments(target.path);
23648
+ target.path = base.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
23611
23649
  }
23612
23650
  target.query = relative2.query;
23613
23651
  }
@@ -23645,12 +23683,13 @@ var require_fast_uri = __commonJS({
23645
23683
  const options = Object.assign({}, opts);
23646
23684
  const uriTokens = [];
23647
23685
  if (component.scheme) {
23648
- component.scheme = decodeValidScheme(component.scheme);
23686
+ component.scheme = validateScheme(component.scheme);
23649
23687
  }
23650
23688
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
23651
23689
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
23652
23690
  const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
23653
- const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
23691
+ const schemelessReference = (component.scheme === void 0 || options.reference === "suffix") && !hasAuthority;
23692
+ const pathNoScheme = !options.skipEscape && schemelessReference;
23654
23693
  if (component.path !== void 0) {
23655
23694
  if (!options.skipEscape) {
23656
23695
  component.path = serializePathEncoding(component.path, pathNoScheme);
@@ -23659,7 +23698,7 @@ var require_fast_uri = __commonJS({
23659
23698
  }
23660
23699
  }
23661
23700
  if (options.reference !== "suffix" && component.scheme) {
23662
- component.scheme = decodeValidScheme(component.scheme);
23701
+ component.scheme = validateScheme(component.scheme);
23663
23702
  uriTokens.push(component.scheme, ":");
23664
23703
  }
23665
23704
  const authority = recomposeAuthority(component);
@@ -23675,9 +23714,9 @@ var require_fast_uri = __commonJS({
23675
23714
  if (component.path !== void 0) {
23676
23715
  let s = component.path;
23677
23716
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
23678
- s = removeDotSegments(s);
23717
+ s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
23679
23718
  }
23680
- if (pathNoScheme) {
23719
+ if (schemelessReference) {
23681
23720
  s = serializePathEncoding(s, true);
23682
23721
  }
23683
23722
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
@@ -23724,16 +23763,34 @@ var require_fast_uri = __commonJS({
23724
23763
  return hasMalformedPercentEncoding(matches[3]) || host2 !== void 0 && !isIPLiteral(host2) && hasMalformedPercentEncoding(host2) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
23725
23764
  }
23726
23765
  function canonicalizeHost(parsed, options, schemeHandler, isIP) {
23766
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
23767
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
23768
+ return true;
23769
+ }
23727
23770
  if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
23771
+ let url;
23728
23772
  try {
23729
- parsed.host = new URL("http://" + parsed.host).hostname;
23773
+ url = new URL("http://" + parsed.host);
23730
23774
  } catch (e) {
23731
23775
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
23732
23776
  return true;
23733
23777
  }
23778
+ if (!isHostOnlyURL(url)) {
23779
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
23780
+ return true;
23781
+ }
23782
+ parsed.host = url.hostname;
23734
23783
  }
23735
23784
  return false;
23736
23785
  }
23786
+ function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
23787
+ return Boolean(
23788
+ parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
23789
+ );
23790
+ }
23791
+ function isHostOnlyURL(url) {
23792
+ return url.username === "" && url.password === "" && url.port === "" && url.pathname === "/" && url.search === "" && url.hash === "" && url.hostname !== "";
23793
+ }
23737
23794
  function parseWithStatus(uri, opts) {
23738
23795
  const options = Object.assign({}, opts);
23739
23796
  const parsed = {
@@ -23788,9 +23845,8 @@ var require_fast_uri = __commonJS({
23788
23845
  parsed.query = matches[7];
23789
23846
  parsed.fragment = matches[8];
23790
23847
  if (parsed.scheme !== void 0) {
23791
- const decodedScheme = unescape(parsed.scheme);
23792
- if (VALID_SCHEME.test(decodedScheme)) {
23793
- parsed.scheme = decodedScheme.toLowerCase();
23848
+ if (VALID_SCHEME.test(parsed.scheme)) {
23849
+ parsed.scheme = parsed.scheme.toLowerCase();
23794
23850
  } else {
23795
23851
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
23796
23852
  malformedScheme = true;
@@ -23818,7 +23874,7 @@ var require_fast_uri = __commonJS({
23818
23874
  malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
23819
23875
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
23820
23876
  if (malformedIPLiteral) {
23821
- parsed.error = parsed.error || "URI host is malformed.";
23877
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
23822
23878
  malformedAuthorityOrPort = true;
23823
23879
  }
23824
23880
  } else {
@@ -23846,7 +23902,7 @@ var require_fast_uri = __commonJS({
23846
23902
  if (!isIP) {
23847
23903
  host2 = normalizePercentEncoding(host2.toLowerCase());
23848
23904
  }
23849
- parsed.host = reescapeHostDelimiters(host2, isIP);
23905
+ parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host2, isIP) : host2;
23850
23906
  }
23851
23907
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
23852
23908
  if (parsed.path) {
@@ -34467,7 +34523,7 @@ var FRAMEWORK_VERSION_POLICY = Object.freeze({
34467
34523
  floors: Object.freeze(["14.4.0"]),
34468
34524
  denied: Object.freeze([])
34469
34525
  }),
34470
- next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
34526
+ next: Object.freeze({ floors: Object.freeze(["16.4.1"]), denied: Object.freeze([]) }),
34471
34527
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
34472
34528
  "@tanstack/react-start": Object.freeze({
34473
34529
  floors: Object.freeze(["1.168.60"]),
@@ -40867,7 +40923,7 @@ function exact3(v, keys) {
40867
40923
  // apps/product-cli/package.json
40868
40924
  var package_default = {
40869
40925
  name: "@ohmyhost/product-cli",
40870
- version: "0.1.37",
40926
+ version: "0.1.38",
40871
40927
  private: true,
40872
40928
  ohmyhost: {
40873
40929
  deployment: "production",
@@ -40899,7 +40955,7 @@ var package_default = {
40899
40955
  },
40900
40956
  scripts: {
40901
40957
  build: "tsc --project tsconfig.build.json",
40902
- typecheck: "tsc --project tsconfig.json --noEmit",
40958
+ typecheck: "tsc --project tsconfig.json --noEmit --incremental --tsBuildInfoFile typecheck.tsbuildinfo",
40903
40959
  test: "vitest run --root ../.. apps/product-cli/test"
40904
40960
  },
40905
40961
  dependencies: {
@@ -48066,7 +48122,7 @@ function normalizeEgressOrigins(value) {
48066
48122
  }
48067
48123
  function forbiddenEgressHostname(hostname) {
48068
48124
  const normalized = hostname.toLowerCase();
48069
- return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal") || /^\d{1,3}(?:\.\d{1,3}){3}$/u.test(normalized) || normalized.startsWith("[") || normalized.endsWith("]");
48125
+ return /^ohmyho-(?:mail|storage)-gateway-[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*\.workers\.dev\.?$/u.test(normalized) || normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal") || /^\d{1,3}(?:\.\d{1,3}){3}$/u.test(normalized) || normalized.startsWith("[") || normalized.endsWith("]");
48070
48126
  }
48071
48127
  function parseOhmyhostConfigYaml(source) {
48072
48128
  if (typeof source !== "string") {
@@ -49197,6 +49253,8 @@ var UTF8 = new TextDecoder("utf-8", { fatal: true });
49197
49253
  var MAXIMUM_SOURCE_BYTES = 2 * 1024 * 1024;
49198
49254
  var SOURCE_MODULE_PATTERN = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
49199
49255
  var STORAGE_CLIENT_CALL_PATTERN = /\bcreatePrivateStorageClient\s*\(/u;
49256
+ var GATEWAY_BINDING_NAME = /^OHMYHOST_(?:MAIL|STORAGE)_GATEWAY$/u;
49257
+ var GATEWAY_WORD_TOKEN = /[\p{ID_Start}_$][\p{ID_Continue}$]*/uy;
49200
49258
  var UNNAMED_MODULE_MESSAGES = Object.freeze({
49201
49259
  database_binding_private: 'The module at path reads a database connection string (DATABASE_URL, HYPERDRIVE, connectionString or a postgres:// URL); none reaches a customer Worker. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". Commit and push the change, then plan the new commit.',
49202
49260
  database_driver_unsupported: 'The module at path imports a database socket driver (pg, postgres, pg-native or mysql2); customer Workers cannot open sockets. Call the database with createPrivateDatabaseClient from "@ohmyhost/customer-runtime/database". Commit and push the change, then plan the new commit.'
@@ -49207,10 +49265,14 @@ function callsStorageClient(source) {
49207
49265
  var STORAGE_RUNTIME_BINDINGS = Object.freeze([
49208
49266
  "OHMYHOST_ENVIRONMENT_ID",
49209
49267
  "OHMYHOST_PROJECT_ID",
49210
- "OHMYHOST_STORAGE_GATEWAY",
49211
49268
  "OHMYHOST_STORAGE_GATEWAY_URL",
49212
49269
  "OHMYHOST_STORAGE_KEY"
49213
49270
  ]);
49271
+ var MAIL_RUNTIME_BINDINGS = Object.freeze([
49272
+ "OHMYHOST_MAIL_GATEWAY_URL",
49273
+ "OHMYHOST_MAIL_KEY",
49274
+ "OHMYHOST_PROJECT_ID"
49275
+ ]);
49214
49276
  var CUSTOMER_RUNTIME_SOURCE = `npm:@amerged/ohmyhost-runtime@${package_default.version}`;
49215
49277
  var PENDING_CHECKS = Object.freeze({
49216
49278
  installed_version: "compatibility.frameworks lists a version range that no lockfile ohmyho.st reads (package-lock.json v2/v3 or pnpm-lock.yaml v6/v9) resolves. Planning admits it; after the frozen install, the build checks the installed version against the same security minimum.",
@@ -49389,6 +49451,7 @@ async function judgeSource(context) {
49389
49451
  ...source.findings.map((finding) => requiredCatalogBlocker(finding, context.applicationRoot)),
49390
49452
  ...await workerModuleBlockers(context),
49391
49453
  ...await databaseContractBlockers(context, context.provider),
49454
+ ...await gatewayBindingBlockers(context),
49392
49455
  ...await storageClientBlockers(context),
49393
49456
  ...await migrationSqlBlockers(context, context.provider)
49394
49457
  ],
@@ -49437,6 +49500,147 @@ async function workerModuleBlockers(context) {
49437
49500
  const source = await readFile3(resolve5(context.applicationDirectory, path2), "utf8");
49438
49501
  return workerModuleFindings({ path: path2, source, fetch: framework === "functions", scheduled }).map((finding) => requiredCatalogBlocker(finding, context.applicationRoot));
49439
49502
  }
49503
+ function gatewaySourceTokens(source) {
49504
+ const tokens = [];
49505
+ let cursor = 0;
49506
+ const quoted = (quote) => {
49507
+ const start = cursor++;
49508
+ while (cursor < source.length) {
49509
+ const character = source[cursor++];
49510
+ if (character === "\\")
49511
+ cursor++;
49512
+ else if (character === quote)
49513
+ break;
49514
+ }
49515
+ tokens.push(source.slice(start, cursor));
49516
+ };
49517
+ const code = (interpolation) => {
49518
+ let braces = 0;
49519
+ while (cursor < source.length) {
49520
+ const character = source[cursor];
49521
+ if (interpolation && character === "}" && braces === 0) {
49522
+ cursor++;
49523
+ return;
49524
+ }
49525
+ if (character === '"' || character === "'") {
49526
+ quoted(character);
49527
+ continue;
49528
+ }
49529
+ if (character === "`") {
49530
+ cursor++;
49531
+ while (cursor < source.length) {
49532
+ const literal = source[cursor++];
49533
+ if (literal === "\\")
49534
+ cursor++;
49535
+ else if (literal === "`")
49536
+ break;
49537
+ else if (literal === "$" && source[cursor] === "{") {
49538
+ cursor++;
49539
+ tokens.push("(");
49540
+ code(true);
49541
+ tokens.push(")");
49542
+ }
49543
+ }
49544
+ continue;
49545
+ }
49546
+ if (/\s/u.test(character)) {
49547
+ cursor++;
49548
+ continue;
49549
+ }
49550
+ if (character === "{")
49551
+ braces++;
49552
+ if (character === "}")
49553
+ braces--;
49554
+ GATEWAY_WORD_TOKEN.lastIndex = cursor;
49555
+ const word = GATEWAY_WORD_TOKEN.exec(source)?.[0];
49556
+ const token = word ?? (["?.", "=>"].includes(source.slice(cursor, cursor + 2)) ? source.slice(cursor, cursor + 2) : character);
49557
+ tokens.push(token);
49558
+ cursor += token.length;
49559
+ }
49560
+ };
49561
+ code(false);
49562
+ return tokens;
49563
+ }
49564
+ function usesGatewayBinding(source) {
49565
+ const tokens = gatewaySourceTokens(sourceWithoutComments(source));
49566
+ const objects = [];
49567
+ let declaration;
49568
+ let declarationDepth = 0;
49569
+ for (let index = 0; index < tokens.length; index++) {
49570
+ const token = tokens[index];
49571
+ const previous = tokens[index - 1];
49572
+ const next = tokens[index + 1];
49573
+ if (declaration !== void 0) {
49574
+ if (declarationDepth === 0 && (token === ";" || /^(?:export|const|let|var|function|class|type|interface)$/u.test(token))) {
49575
+ declaration = void 0;
49576
+ if (token === ";")
49577
+ continue;
49578
+ } else {
49579
+ if (token === "{" || token === "(" || token === "[")
49580
+ declarationDepth++;
49581
+ if (token === "}" || token === ")" || token === "]") {
49582
+ declarationDepth--;
49583
+ if (declaration !== "type" && declarationDepth === 0)
49584
+ declaration = void 0;
49585
+ }
49586
+ continue;
49587
+ }
49588
+ }
49589
+ if ((token === "type" || token === "interface") && next !== void 0 && /^[\p{ID_Start}_$][\p{ID_Continue}$]*$/u.test(next) && previous !== "." && previous !== "?.") {
49590
+ declaration = token;
49591
+ continue;
49592
+ }
49593
+ if (token === "{" && previous === ":" && /^(?:const|let|var)$/u.test(tokens[index - 3] ?? "")) {
49594
+ declaration = "annotation";
49595
+ declarationDepth = 1;
49596
+ continue;
49597
+ }
49598
+ if (token === "{")
49599
+ objects.push(false);
49600
+ if (token === "}") {
49601
+ const gateway = objects.pop();
49602
+ let after = index + 1;
49603
+ if (tokens[after] === ":") {
49604
+ after++;
49605
+ while (after < tokens.length && !/^(?:=|;|\{|\})$/u.test(tokens[after]))
49606
+ after++;
49607
+ }
49608
+ if (gateway && tokens[after] === "=")
49609
+ return true;
49610
+ }
49611
+ const quoted = token.startsWith('"') || token.startsWith("'");
49612
+ const name = quoted ? token.slice(1, -1) : token;
49613
+ if (!GATEWAY_BINDING_NAME.test(name))
49614
+ continue;
49615
+ if (!quoted && (previous === "." || previous === "?."))
49616
+ return true;
49617
+ const receiver = tokens[index - (tokens[index - 2] === "?." ? 3 : 2)] ?? "";
49618
+ if (quoted && previous === "[" && next === "]" && (/^[\p{ID_Start}_$][\p{ID_Continue}$]*$/u.test(receiver) || receiver === ")" || receiver === "]") && !/^(?:return|throw|yield|case|new|typeof|void|delete|await|in|of)$/u.test(receiver))
49619
+ return true;
49620
+ if (objects.length > 0 && (previous === "{" || previous === ",") && (next === ":" || next === "," || next === "}" || next === "="))
49621
+ objects[objects.length - 1] = true;
49622
+ }
49623
+ return false;
49624
+ }
49625
+ async function gatewayBindingBlockers(context) {
49626
+ const blockers = [];
49627
+ for (const path2 of context.files) {
49628
+ if (!SOURCE_MODULE_PATTERN.test(path2) || /\.d\.[cm]?ts$/u.test(path2))
49629
+ continue;
49630
+ const absolute = resolve5(context.applicationDirectory, path2);
49631
+ const information = await stat2(absolute);
49632
+ if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES)
49633
+ continue;
49634
+ if (!usesGatewayBinding(await readFile3(absolute, "utf8")))
49635
+ continue;
49636
+ blockers.push({
49637
+ code: "gateway_binding_unsupported",
49638
+ path: repositoryPath(context.applicationRoot, path2),
49639
+ message: "Mail and storage gateway Service Bindings are unsupported. In server code, create the client from its OHMYHOST_MAIL_GATEWAY_URL or OHMYHOST_STORAGE_GATEWAY_URL, environment key and project ID, plus the environment ID for storage. Use the global fetch with fetch: (request) => fetch(request), and capabilityFetch: (request) => fetch(request) for storage signed URLs."
49640
+ });
49641
+ }
49642
+ return blockers;
49643
+ }
49440
49644
  async function storageClientBlockers(context) {
49441
49645
  if (context.config?.storage === void 0)
49442
49646
  return [];
@@ -49454,7 +49658,7 @@ async function storageClientBlockers(context) {
49454
49658
  {
49455
49659
  code: "storage_client_missing",
49456
49660
  path: null,
49457
- message: 'ohmyhost.yaml declares storage, but no application source calls createPrivateStorageClient. Import it from "@ohmyhost/customer-runtime/storage" and build it in server code from OHMYHOST_STORAGE_GATEWAY, OHMYHOST_STORAGE_GATEWAY_URL, OHMYHOST_PROJECT_ID, OHMYHOST_ENVIRONMENT_ID and OHMYHOST_STORAGE_KEY, then reserve an upload, PUT the bytes to the returned signed URL, complete it, and serve reads with createSignedRead. There is no FILES bucket binding.'
49661
+ message: 'ohmyhost.yaml declares storage, but no application source calls createPrivateStorageClient. Import it from "@ohmyhost/customer-runtime/storage" and build it in server code from OHMYHOST_STORAGE_GATEWAY_URL, OHMYHOST_PROJECT_ID, OHMYHOST_ENVIRONMENT_ID and OHMYHOST_STORAGE_KEY, with the global fetch as fetch: (request) => fetch(request) and capabilityFetch: (request) => fetch(request). Reserve an upload, PUT the bytes to the returned signed URL, complete it, and serve reads with createSignedRead. There is no FILES bucket binding.'
49458
49662
  }
49459
49663
  ];
49460
49664
  }
@@ -49969,10 +50173,12 @@ async function detectR2Storage(root, files) {
49969
50173
  function runtimeContract(capabilities) {
49970
50174
  return Object.freeze({
49971
50175
  bindings: Object.freeze([
49972
- ...capabilities.auth ? ["BETTER_AUTH_SECRET"] : [],
49973
- ...capabilities.storage ? STORAGE_RUNTIME_BINDINGS : [],
49974
- ...capabilities.database ? ["OHMYHOST_DATABASE"] : [],
49975
- ...capabilities.mail ? ["OHMYHOST_MAIL_KEY"] : []
50176
+ .../* @__PURE__ */ new Set([
50177
+ ...capabilities.auth ? ["BETTER_AUTH_SECRET"] : [],
50178
+ ...capabilities.storage ? STORAGE_RUNTIME_BINDINGS : [],
50179
+ ...capabilities.database ? ["OHMYHOST_DATABASE"] : [],
50180
+ ...capabilities.mail ? MAIL_RUNTIME_BINDINGS : []
50181
+ ])
49976
50182
  ].sort()),
49977
50183
  packages: Object.freeze({
49978
50184
  ...capabilities.auth ? { betterAuth: MANAGED_BETTER_AUTH_VERSION } : {},
package/dist/index.js CHANGED
@@ -99,7 +99,7 @@ import {
99
99
  selectWorkspace,
100
100
  verifiedUserId,
101
101
  workspaceOf
102
- } from "./chunk-IHXNADOQ.js";
102
+ } from "./chunk-EWKJGJQU.js";
103
103
  import {
104
104
  CredentialStoreIntegrityError,
105
105
  CredentialStoreUnavailableError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amerged/ohmyhost-cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/product-cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsc --project tsconfig.build.json",
35
- "typecheck": "tsc --project tsconfig.json --noEmit",
35
+ "typecheck": "tsc --project tsconfig.json --noEmit --incremental --tsBuildInfoFile typecheck.tsbuildinfo",
36
36
  "test": "vitest run --root ../.. apps/product-cli/test"
37
37
  },
38
38
  "dependencies": {
@@ -75,6 +75,8 @@ export type RepositoryFramework = Exclude<FrameworkFamilyClassification, "ambigu
75
75
  const MAXIMUM_SOURCE_BYTES = 2 * 1024 * 1024;
76
76
  const SOURCE_MODULE_PATTERN = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
77
77
  const STORAGE_CLIENT_CALL_PATTERN = /\bcreatePrivateStorageClient\s*\(/u;
78
+ const GATEWAY_BINDING_NAME = /^OHMYHOST_(?:MAIL|STORAGE)_GATEWAY$/u;
79
+ const GATEWAY_WORD_TOKEN = /[\p{ID_Start}_$][\p{ID_Continue}$]*/uy;
78
80
  // The catalog names a module only by a path of its grammar; a file name with a space, for example,
79
81
  // is not one, so its blocker carries the path beside a sentence that does not quote it.
80
82
  const UNNAMED_MODULE_MESSAGES: Readonly<Record<DatabaseContractCode, string>> = Object.freeze({
@@ -89,15 +91,19 @@ function callsStorageClient(source: string): boolean {
89
91
  return STORAGE_CLIENT_CALL_PATTERN.test(sourceWithoutComments(source));
90
92
  }
91
93
 
92
- // Storage is reached through a private Service Binding plus the three values and the key that
93
- // `createPrivateStorageClient` reads. No raw bucket binding is handed to customer code.
94
+ // Global fetch reaches the private storage gateway using these values and its environment key.
95
+ // No raw bucket binding is handed to customer code.
94
96
  const STORAGE_RUNTIME_BINDINGS = Object.freeze([
95
97
  "OHMYHOST_ENVIRONMENT_ID",
96
98
  "OHMYHOST_PROJECT_ID",
97
- "OHMYHOST_STORAGE_GATEWAY",
98
99
  "OHMYHOST_STORAGE_GATEWAY_URL",
99
100
  "OHMYHOST_STORAGE_KEY",
100
101
  ]);
102
+ const MAIL_RUNTIME_BINDINGS = Object.freeze([
103
+ "OHMYHOST_MAIL_GATEWAY_URL",
104
+ "OHMYHOST_MAIL_KEY",
105
+ "OHMYHOST_PROJECT_ID",
106
+ ]);
101
107
  // The runtime release is published unchanged as `@amerged/ohmyhost-runtime`; this pinned alias keeps
102
108
  // its import name and, unlike the website archive each release removes, stays installable.
103
109
  const CUSTOMER_RUNTIME_SOURCE = `npm:@amerged/ohmyhost-runtime@${cliPackage.version}`;
@@ -105,6 +111,7 @@ const CUSTOMER_RUNTIME_SOURCE = `npm:@amerged/ohmyhost-runtime@${cliPackage.vers
105
111
  /** A catalog code, which planning answers with the same sentence, or a check only init runs. */
106
112
  export type RepositoryInitBlockerCode =
107
113
  | FrameworkConversionDiagnosticCode
114
+ | "gateway_binding_unsupported"
108
115
  | "migration_filename_noncanonical"
109
116
  | "migration_sql_not_admitted"
110
117
  | "storage_client_missing";
@@ -507,7 +514,7 @@ interface SourceCheckContext {
507
514
  /**
508
515
  * Every blocker of a source with a valid or proposed configuration, in the order planning judges:
509
516
  * migration filenames, the shared source rules, the Worker module handlers and the database
510
- * contract; init's own storage client and migration SQL checks come last.
517
+ * contract; init's own gateway binding, storage client and migration SQL checks come last.
511
518
  */
512
519
  async function judgeSource(
513
520
  context: SourceCheckContext &
@@ -528,6 +535,7 @@ async function judgeSource(
528
535
  ...source.findings.map((finding) => requiredCatalogBlocker(finding, context.applicationRoot)),
529
536
  ...(await workerModuleBlockers(context)),
530
537
  ...(await databaseContractBlockers(context, context.provider)),
538
+ ...(await gatewayBindingBlockers(context)),
531
539
  ...(await storageClientBlockers(context)),
532
540
  ...(await migrationSqlBlockers(context, context.provider)),
533
541
  ],
@@ -621,6 +629,168 @@ async function workerModuleBlockers(context: SourceCheckContext): Promise<Reposi
621
629
  );
622
630
  }
623
631
 
632
+ /** Keep literal text inert, but scan executable template interpolations as ordinary code. */
633
+ function gatewaySourceTokens(source: string): readonly string[] {
634
+ const tokens: string[] = [];
635
+ let cursor = 0;
636
+ const quoted = (quote: string) => {
637
+ const start = cursor++;
638
+ while (cursor < source.length) {
639
+ const character = source[cursor++];
640
+ if (character === "\\") cursor++;
641
+ else if (character === quote) break;
642
+ }
643
+ tokens.push(source.slice(start, cursor));
644
+ };
645
+ const code = (interpolation: boolean): void => {
646
+ let braces = 0;
647
+ while (cursor < source.length) {
648
+ const character = source[cursor] as string;
649
+ if (interpolation && character === "}" && braces === 0) {
650
+ cursor++;
651
+ return;
652
+ }
653
+ if (character === '"' || character === "'") {
654
+ quoted(character);
655
+ continue;
656
+ }
657
+ if (character === "`") {
658
+ cursor++;
659
+ while (cursor < source.length) {
660
+ const literal = source[cursor++];
661
+ if (literal === "\\") cursor++;
662
+ else if (literal === "`") break;
663
+ else if (literal === "$" && source[cursor] === "{") {
664
+ cursor++;
665
+ tokens.push("(");
666
+ code(true);
667
+ tokens.push(")");
668
+ }
669
+ }
670
+ continue;
671
+ }
672
+ if (/\s/u.test(character)) {
673
+ cursor++;
674
+ continue;
675
+ }
676
+ if (character === "{") braces++;
677
+ if (character === "}") braces--;
678
+ GATEWAY_WORD_TOKEN.lastIndex = cursor;
679
+ const word = GATEWAY_WORD_TOKEN.exec(source)?.[0];
680
+ const token =
681
+ word ??
682
+ (["?.", "=>"].includes(source.slice(cursor, cursor + 2))
683
+ ? source.slice(cursor, cursor + 2)
684
+ : character);
685
+ tokens.push(token);
686
+ cursor += token.length;
687
+ }
688
+ };
689
+ code(false);
690
+ return tokens;
691
+ }
692
+
693
+ /** Static value accesses only: declarations, literal messages and object-literal keys are inert. */
694
+ function usesGatewayBinding(source: string): boolean {
695
+ const tokens = gatewaySourceTokens(sourceWithoutComments(source));
696
+ const objects: boolean[] = [];
697
+ let declaration: "type" | "interface" | "annotation" | undefined;
698
+ let declarationDepth = 0;
699
+ for (let index = 0; index < tokens.length; index++) {
700
+ const token = tokens[index] as string;
701
+ const previous = tokens[index - 1];
702
+ const next = tokens[index + 1];
703
+ if (declaration !== undefined) {
704
+ if (
705
+ declarationDepth === 0 &&
706
+ (token === ";" || /^(?:export|const|let|var|function|class|type|interface)$/u.test(token))
707
+ ) {
708
+ declaration = undefined;
709
+ if (token === ";") continue;
710
+ } else {
711
+ if (token === "{" || token === "(" || token === "[") declarationDepth++;
712
+ if (token === "}" || token === ")" || token === "]") {
713
+ declarationDepth--;
714
+ if (declaration !== "type" && declarationDepth === 0) declaration = undefined;
715
+ }
716
+ continue;
717
+ }
718
+ }
719
+ if (
720
+ (token === "type" || token === "interface") &&
721
+ next !== undefined &&
722
+ /^[\p{ID_Start}_$][\p{ID_Continue}$]*$/u.test(next) &&
723
+ previous !== "." &&
724
+ previous !== "?."
725
+ ) {
726
+ declaration = token;
727
+ continue;
728
+ }
729
+ // An inline variable type is not an object binding pattern; resume at its initializer.
730
+ if (token === "{" && previous === ":" && /^(?:const|let|var)$/u.test(tokens[index - 3] ?? "")) {
731
+ declaration = "annotation";
732
+ declarationDepth = 1;
733
+ continue;
734
+ }
735
+ if (token === "{") objects.push(false);
736
+ if (token === "}") {
737
+ const gateway = objects.pop();
738
+ let after = index + 1;
739
+ if (tokens[after] === ":") {
740
+ after++;
741
+ while (after < tokens.length && !/^(?:=|;|\{|\})$/u.test(tokens[after] as string)) after++;
742
+ }
743
+ if (gateway && tokens[after] === "=") return true;
744
+ }
745
+ const quoted = token.startsWith('"') || token.startsWith("'");
746
+ const name = quoted ? token.slice(1, -1) : token;
747
+ if (!GATEWAY_BINDING_NAME.test(name)) continue;
748
+ if (!quoted && (previous === "." || previous === "?.")) return true;
749
+ const receiver = tokens[index - (tokens[index - 2] === "?." ? 3 : 2)] ?? "";
750
+ if (
751
+ quoted &&
752
+ previous === "[" &&
753
+ next === "]" &&
754
+ (/^[\p{ID_Start}_$][\p{ID_Continue}$]*$/u.test(receiver) ||
755
+ receiver === ")" ||
756
+ receiver === "]") &&
757
+ !/^(?:return|throw|yield|case|new|typeof|void|delete|await|in|of)$/u.test(receiver)
758
+ )
759
+ return true;
760
+ if (
761
+ objects.length > 0 &&
762
+ (previous === "{" || previous === ",") &&
763
+ (next === ":" || next === "," || next === "}" || next === "=")
764
+ )
765
+ objects[objects.length - 1] = true;
766
+ }
767
+ return false;
768
+ }
769
+
770
+ /**
771
+ * Init names legacy mail/storage bindings before a customer builds code whose gateway calls the
772
+ * egress policy cannot serve. This local guidance does not change planning's admission policy.
773
+ */
774
+ async function gatewayBindingBlockers(
775
+ context: SourceCheckContext,
776
+ ): Promise<RepositoryInitBlocker[]> {
777
+ const blockers: RepositoryInitBlocker[] = [];
778
+ for (const path of context.files) {
779
+ if (!SOURCE_MODULE_PATTERN.test(path) || /\.d\.[cm]?ts$/u.test(path)) continue;
780
+ const absolute = resolve(context.applicationDirectory, path);
781
+ const information = await stat(absolute);
782
+ if (!information.isFile() || information.size > MAXIMUM_SOURCE_BYTES) continue;
783
+ if (!usesGatewayBinding(await readFile(absolute, "utf8"))) continue;
784
+ blockers.push({
785
+ code: "gateway_binding_unsupported",
786
+ path: repositoryPath(context.applicationRoot, path),
787
+ message:
788
+ "Mail and storage gateway Service Bindings are unsupported. In server code, create the client from its OHMYHOST_MAIL_GATEWAY_URL or OHMYHOST_STORAGE_GATEWAY_URL, environment key and project ID, plus the environment ID for storage. Use the global fetch with fetch: (request) => fetch(request), and capabilityFetch: (request) => fetch(request) for storage signed URLs.",
789
+ });
790
+ }
791
+ return blockers;
792
+ }
793
+
624
794
  /**
625
795
  * Declared storage is reached only through the private gateway client. A storage-enabled app whose
626
796
  * source never constructs that client deploys green and fails at its first upload, so name it here.
@@ -641,7 +811,7 @@ async function storageClientBlockers(
641
811
  code: "storage_client_missing",
642
812
  path: null,
643
813
  message:
644
- 'ohmyhost.yaml declares storage, but no application source calls createPrivateStorageClient. Import it from "@ohmyhost/customer-runtime/storage" and build it in server code from OHMYHOST_STORAGE_GATEWAY, OHMYHOST_STORAGE_GATEWAY_URL, OHMYHOST_PROJECT_ID, OHMYHOST_ENVIRONMENT_ID and OHMYHOST_STORAGE_KEY, then reserve an upload, PUT the bytes to the returned signed URL, complete it, and serve reads with createSignedRead. There is no FILES bucket binding.',
814
+ 'ohmyhost.yaml declares storage, but no application source calls createPrivateStorageClient. Import it from "@ohmyhost/customer-runtime/storage" and build it in server code from OHMYHOST_STORAGE_GATEWAY_URL, OHMYHOST_PROJECT_ID, OHMYHOST_ENVIRONMENT_ID and OHMYHOST_STORAGE_KEY, with the global fetch as fetch: (request) => fetch(request) and capabilityFetch: (request) => fetch(request). Reserve an upload, PUT the bytes to the returned signed URL, complete it, and serve reads with createSignedRead. There is no FILES bucket binding.',
645
815
  },
646
816
  ];
647
817
  }
@@ -1418,10 +1588,12 @@ function runtimeContract(capabilities: RuntimeCapabilities): Readonly<{
1418
1588
  return Object.freeze({
1419
1589
  bindings: Object.freeze(
1420
1590
  [
1421
- ...(capabilities.auth ? ["BETTER_AUTH_SECRET"] : []),
1422
- ...(capabilities.storage ? STORAGE_RUNTIME_BINDINGS : []),
1423
- ...(capabilities.database ? ["OHMYHOST_DATABASE"] : []),
1424
- ...(capabilities.mail ? ["OHMYHOST_MAIL_KEY"] : []),
1591
+ ...new Set([
1592
+ ...(capabilities.auth ? ["BETTER_AUTH_SECRET"] : []),
1593
+ ...(capabilities.storage ? STORAGE_RUNTIME_BINDINGS : []),
1594
+ ...(capabilities.database ? ["OHMYHOST_DATABASE"] : []),
1595
+ ...(capabilities.mail ? MAIL_RUNTIME_BINDINGS : []),
1596
+ ]),
1425
1597
  ].sort(),
1426
1598
  ),
1427
1599
  packages: Object.freeze({
@@ -150,7 +150,7 @@
150
150
  },
151
151
  "scripts": {
152
152
  "build": "tsc --project tsconfig.build.json",
153
- "typecheck": "tsc --project tsconfig.json --noEmit",
153
+ "typecheck": "tsc --project tsconfig.json --noEmit --incremental --tsBuildInfoFile typecheck.tsbuildinfo",
154
154
  "test": "vitest run --root ../.. packages/contracts/test",
155
155
  "validate": "pnpm run typecheck && pnpm run test"
156
156
  },
@@ -48,7 +48,7 @@ const CORPUS_NEXT_PAGE = `export default function Home() {
48
48
  return <main>Hello from ohmyho.st</main>;
49
49
  }
50
50
  `;
51
- // The config `create-next-app@16.4.0 --yes --no-tailwind` writes: Cache Components with partial
51
+ // The config `create-next-app@16.4.1 --yes --no-tailwind` writes: Cache Components with partial
52
52
  // prefetching.
53
53
  const CREATE_NEXT_APP_CONFIG = `import type { NextConfig } from "next";
54
54
 
@@ -124,10 +124,7 @@ export const FRAMEWORK_ADMISSION_CONFORMANCE_CORPUS: Readonly<{
124
124
  Readonly<{ diagnostic: FrameworkConversionDiagnostic }>)[];
125
125
  }> = Object.freeze({
126
126
  admitted: Object.freeze([
127
- corpusCase(
128
- "Next.js 16.3.8 with npm on the edge runtime (the exact customer source)",
129
- nextSource(),
130
- ),
127
+ corpusCase("Next.js 16.4.1 with npm on the edge runtime", nextSource()),
131
128
  corpusCase("Next.js 17.0.0, a later stable major", nextSource({ declared: "17.0.0" })),
132
129
  corpusCase(
133
130
  "Vite ^8.2.0 resolved to 8.3.4 by pnpm-lock.yaml",
@@ -147,26 +144,26 @@ export const FRAMEWORK_ADMISSION_CONFORMANCE_CORPUS: Readonly<{
147
144
  Object.freeze({
148
145
  ...corpusCase(
149
146
  "Next.js ^16.3.0 with bun.lock, which ohmyho.st does not resolve",
150
- nextSource({ declared: "^16.3.0", resolved: "16.4.0", manager: "bun" }),
147
+ nextSource({ declared: "^16.3.0", resolved: "16.4.1", manager: "bun" }),
151
148
  ),
152
149
  packages: Object.freeze(["next"] as const),
153
150
  }),
154
151
  ]),
155
152
  refused: Object.freeze([
156
153
  refusedCase(
157
- "Next.js 16.3.7 below the 16.x security minimum",
158
- nextSource({ declared: "16.3.7" }),
159
- versionFinding("package-lock.json", "next", "16.3.7", "16.3.8"),
154
+ "Next.js 16.4.0 below the 16.x security minimum",
155
+ nextSource({ declared: "16.4.0" }),
156
+ versionFinding("package-lock.json", "next", "16.4.0", "16.4.1"),
160
157
  ),
161
158
  refusedCase(
162
- "Next.js 15.5.26 declared with yarn below the 15.x security minimum",
163
- nextSource({ declared: "15.5.26", manager: "yarn" }),
164
- versionFinding("package.json", "next", "15.5.26", "15.5.27"),
159
+ "Next.js 15.5.28 declared with yarn, because no 15.x release is admitted",
160
+ nextSource({ declared: "15.5.28", manager: "yarn" }),
161
+ versionFinding("package.json", "next", "15.5.28", "16.4.1"),
165
162
  ),
166
163
  refusedCase(
167
- "Next.js ^16.3.0 resolved to 16.3.7 by package-lock.json",
168
- nextSource({ declared: "^16.3.0", resolved: "16.3.7" }),
169
- versionFinding("package-lock.json", "next", "16.3.7", "16.3.8"),
164
+ "Next.js ^16.3.0 resolved to 16.4.0 by package-lock.json",
165
+ nextSource({ declared: "^16.3.0", resolved: "16.4.0" }),
166
+ versionFinding("package-lock.json", "next", "16.4.0", "16.4.1"),
170
167
  ),
171
168
  refusedCase(
172
169
  "TanStack Start 1.168.59 resolved by pnpm-lock.yaml below its security minimum",
@@ -354,7 +351,7 @@ function nextSource(
354
351
  ): Readonly<Record<string, string>> {
355
352
  const manager = options.manager ?? "npm";
356
353
  const [packageManager, lockfile] = CORPUS_MANAGERS[manager];
357
- const next = options.declared ?? "16.3.8";
354
+ const next = options.declared ?? "16.4.1";
358
355
  const react = { react: "19.1.2", "react-dom": "19.1.2" };
359
356
  const runtime = options.field === "devDependencies" ? react : { next, ...react };
360
357
  return Object.freeze({
@@ -380,7 +377,7 @@ function nextSource(
380
377
  }
381
378
 
382
379
  /**
383
- * The next.config.ts and Tailwind CSS files and packages `create-next-app@16.4.0 --yes` writes, in
380
+ * The next.config.ts and Tailwind CSS files and packages `create-next-app@16.4.1 --yes` writes, in
384
381
  * an App Router source with the packageManager, lockfile and ohmyhost.yaml ohmyho.st requires;
385
382
  * `tailwind: false` is its --no-tailwind output. On Turbopack, its default bundler, it adds only the
386
383
  * Turbopack rule for Tailwind CSS; `postcss` adds the postcss.config.mjs and @tailwindcss/postcss it
@@ -390,7 +387,7 @@ function createNextAppSource(
390
387
  options: Readonly<{ tailwind: boolean; postcss?: boolean }>,
391
388
  ): Readonly<Record<string, string>> {
392
389
  const [packageManager, lockfile] = CORPUS_MANAGERS.npm;
393
- const runtime = { next: "16.4.0", react: "19.3.0", "react-dom": "19.3.0" };
390
+ const runtime = { next: "16.4.1", react: "19.3.0", "react-dom": "19.3.0" };
394
391
  const postcss = options.postcss === true;
395
392
  return Object.freeze({
396
393
  "ohmyhost.yaml": corpusConfiguration("npm", ".open-next/assets", "edge"),
@@ -15,8 +15,15 @@ export const PINNED_BUN_VERSION = "1.2.22";
15
15
  * upper bound: later patches, minors and majors reach the real build, which reports adapter failures.
16
16
  * Denied ranges are half-open [introduced, fixed); a null fix denies every later release.
17
17
  *
18
- * next: GHSA-4jqv-mc3x-m676 and GHSA-mcj8-r9mp-w47p (fixed in 15.5.27 and 16.3.8); GHSA-cjq9-62q9-8jv4,
19
- * GHSA-f87g-xv8r-7p7x, GHSA-3w37-wq28-93x7, GHSA-h694-7cp9-m8p3 and GHSA-39w2-rjm5-chcv (16.3.8).
18
+ * next: GHSA-2464-fhr4-w9xw (critical, remote code execution through the Image Optimization API with
19
+ * SVG and sharp before 0.35.5) and GHSA-p3m5-59mp-jv5r (high, denial of service through Server
20
+ * Actions), both fixed in 15.5.28 and 16.4.1; GHSA-673q-mjm6-r5xr (critical, remote code execution
21
+ * through `use cache` functions that return typed arrays, Next.js 16 only, fixed in 16.4.1).
22
+ * There is no 15.x floor: the pinned OpenNext has refused Next.js 15 since 21 October 2026, so every 15.x
23
+ * release is refused with the minimum 16.4.1 instead of sending a customer to 15.5.28 and a failing build.
24
+ * Earlier floors: GHSA-4jqv-mc3x-m676 and GHSA-mcj8-r9mp-w47p (fixed in 15.5.27 and 16.3.8);
25
+ * GHSA-cjq9-62q9-8jv4, GHSA-f87g-xv8r-7p7x, GHSA-3w37-wq28-93x7, GHSA-h694-7cp9-m8p3 and
26
+ * GHSA-39w2-rjm5-chcv (16.3.8).
20
27
  * @tanstack/react-start: GHSA-qx66-fv34-fjm8 (fixed in 1.168.60).
21
28
  * vite: GHSA-64vr-g452-qvp3 (fixed in 5.4.6), the only advisory affecting `vite build` output.
22
29
  * @type {FrameworkVersionPolicy}
@@ -27,7 +34,7 @@ export const FRAMEWORK_VERSION_POLICY = Object.freeze({
27
34
  floors: Object.freeze(["14.4.0"]),
28
35
  denied: Object.freeze([]),
29
36
  }),
30
- next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
37
+ next: Object.freeze({ floors: Object.freeze(["16.4.1"]), denied: Object.freeze([]) }),
31
38
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
32
39
  "@tanstack/react-start": Object.freeze({
33
40
  floors: Object.freeze(["1.168.60"]),
@@ -209,6 +209,9 @@ function normalizeEgressOrigins(value: readonly string[] | undefined): readonly
209
209
  function forbiddenEgressHostname(hostname: string): boolean {
210
210
  const normalized = hostname.toLowerCase();
211
211
  return (
212
+ /^ohmyho-(?:mail|storage)-gateway-[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*\.workers\.dev\.?$/u.test(
213
+ normalized,
214
+ ) ||
212
215
  normalized === "localhost" ||
213
216
  normalized.endsWith(".localhost") ||
214
217
  normalized.endsWith(".local") ||
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/sdk-ts",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -14,7 +14,7 @@
14
14
  "build": "tsc --project tsconfig.build.json",
15
15
  "check": "pnpm --dir ../.. sdk-ts:check",
16
16
  "generate": "pnpm --dir ../.. sdk-ts:generate",
17
- "typecheck": "tsc --project tsconfig.json --noEmit",
17
+ "typecheck": "tsc --project tsconfig.json --noEmit --incremental --tsBuildInfoFile typecheck.tsbuildinfo",
18
18
  "test": "pnpm run check && vitest run --root ../.. packages/sdk-ts"
19
19
  },
20
20
  "license": "Apache-2.0"
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsc --project tsconfig.build.json",
35
- "typecheck": "tsc --project tsconfig.json --noEmit",
35
+ "typecheck": "tsc --project tsconfig.json --noEmit --incremental --tsBuildInfoFile typecheck.tsbuildinfo",
36
36
  "test": "vitest run --root ../.. packages/workos-auth-contracts/src"
37
37
  },
38
38
  "license": "Apache-2.0"