@amerged/ohmyhost-cli 0.1.33 → 0.1.35

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.33.
3
+ ohmyho.st cli client, version 0.1.35.
4
4
 
5
5
  ```sh
6
6
  npm install --global @amerged/ohmyhost-cli
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-3RDHFA76.js";
5
+ } from "./chunk-ZQSLRNH6.js";
6
6
  import "./chunk-7D2RFLWM.js";
7
7
 
8
8
  // apps/product-cli/dist/bin.js
@@ -34462,6 +34462,11 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
34462
34462
  // packages/contracts/dist/framework-build-script.mjs
34463
34463
  var PINNED_BUN_VERSION = "1.2.22";
34464
34464
  var FRAMEWORK_VERSION_POLICY = Object.freeze({
34465
+ astro: Object.freeze({ floors: Object.freeze(["7.4.0"]), denied: Object.freeze([]) }),
34466
+ "@astrojs/cloudflare": Object.freeze({
34467
+ floors: Object.freeze(["14.4.0"]),
34468
+ denied: Object.freeze([])
34469
+ }),
34465
34470
  next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
34466
34471
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
34467
34472
  "@tanstack/react-start": Object.freeze({
@@ -34539,6 +34544,8 @@ var TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
34539
34544
  function isAdmittedFrameworkBuildScript(framework, script) {
34540
34545
  if (typeof script !== "string")
34541
34546
  return false;
34547
+ if (framework === "astro")
34548
+ return script === "astro build";
34542
34549
  if (framework === "nextjs")
34543
34550
  return script === "next build" || script === "next build --webpack";
34544
34551
  if (framework !== "vite" && framework !== "tanstack-start")
@@ -34728,11 +34735,15 @@ function frameworkVersionDiagnostic(admission, lockfile) {
34728
34735
  });
34729
34736
  }
34730
34737
  var FRAMEWORK_VERSION_PACKAGES = Object.freeze([
34738
+ "astro",
34739
+ "@astrojs/cloudflare",
34731
34740
  "next",
34732
34741
  "vite",
34733
34742
  "@tanstack/react-start"
34734
34743
  ]);
34735
34744
  var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
34745
+ "astro",
34746
+ "@astrojs/cloudflare",
34736
34747
  "next",
34737
34748
  "react",
34738
34749
  "react-dom",
@@ -34743,10 +34754,23 @@ var FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
34743
34754
  "@cloudflare/vite-plugin"
34744
34755
  ]);
34745
34756
  var REACT_PACKAGES = Object.freeze(["react", "react-dom"]);
34746
- var BUILD_FRAMEWORKS = Object.freeze(["nextjs", "vite-static", "tanstack-start"]);
34757
+ var BUILD_FRAMEWORKS = Object.freeze([
34758
+ "astro",
34759
+ "nextjs",
34760
+ "vite-static",
34761
+ "tanstack-start"
34762
+ ]);
34747
34763
  var PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
34748
34764
  var VITE_BUILD_SCRIPT = '"vite build", optionally joined with && to one "tsc", "tsc -b", "tsc --build" or "tsc --noEmit" stage';
34749
34765
  var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34766
+ astro_template_import_unsupported: {
34767
+ paths: ["module"],
34768
+ template: '"{{path}}" uses a dynamic import, import.meta or require in an Astro template. Move server imports into frontmatter and dynamic browser imports into a normal statically imported browser module, then commit and push the change and plan the new commit.'
34769
+ },
34770
+ astro_config_unsupported: {
34771
+ paths: ["framework_config"],
34772
+ template: '"{{path}}" must use the standard Astro output directories and root base. For edge use output: "server", @astrojs/cloudflare with imageService: "passthrough", and session: false. Static hosting must omit a server adapter. Additional Cloudflare bindings, custom sessions and provider cache are not supported. Commit and push the change, then plan the new commit.'
34773
+ },
34750
34774
  managed_auth_database_required: {
34751
34775
  paths: ["repository_config"],
34752
34776
  template: managedCustomerAuthDatabaseMessage("{{path}}")
@@ -34821,7 +34845,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34821
34845
  text: {
34822
34846
  nextjs: 'exactly "next build" or "next build --webpack"; ohmyho.st adds the OpenNext build itself',
34823
34847
  "vite-static": VITE_BUILD_SCRIPT,
34824
- "tanstack-start": VITE_BUILD_SCRIPT
34848
+ "tanstack-start": VITE_BUILD_SCRIPT,
34849
+ astro: 'exactly "astro build"'
34825
34850
  }
34826
34851
  }
34827
34852
  },
@@ -34837,11 +34862,11 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34837
34862
  },
34838
34863
  framework_unsupported: {
34839
34864
  paths: ["manifest"],
34840
- template: `"{{path}}" declares none of next, vite or @tanstack/react-start. Add the app's framework to dependencies, or set runtime.mode: functions in ohmyhost.yaml and add src/ohmyhost/worker.ts. ${PUSH_AND_PLAN}`
34865
+ template: `"{{path}}" declares none of astro, next, vite or @tanstack/react-start. Add the app's framework to dependencies, or set runtime.mode: functions in ohmyhost.yaml and add src/ohmyhost/worker.ts. ${PUSH_AND_PLAN}`
34841
34866
  },
34842
34867
  framework_ambiguous: {
34843
34868
  paths: ["manifest"],
34844
- template: '"{{path}}" declares next together with vite or @tanstack/react-start, so ohmyho.st cannot choose one framework. Remove the packages of the framework this application does not use, commit and push the change, then plan the new commit.'
34869
+ template: '"{{path}}" declares competing application framework packages, so ohmyho.st cannot choose one framework. Remove the packages of the framework this application does not use, commit and push the change, then plan the new commit.'
34845
34870
  },
34846
34871
  lockfile_missing: {
34847
34872
  paths: ["manifest"],
@@ -34880,7 +34905,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34880
34905
  text: {
34881
34906
  nextjs: "runtime.mode: edge",
34882
34907
  "vite-static": "runtime.mode: edge with src/ohmyhost/companion.ts when database, auth, mail, storage or functions.crons is set, and static otherwise",
34883
- "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage"
34908
+ "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage",
34909
+ astro: "runtime.mode: static for prerendered sites, or edge with the Cloudflare adapter"
34884
34910
  }
34885
34911
  }
34886
34912
  },
@@ -34895,7 +34921,8 @@ var FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
34895
34921
  text: {
34896
34922
  nextjs: ".open-next/assets",
34897
34923
  "vite-static": "dist",
34898
- "tanstack-start": "dist/client"
34924
+ "tanstack-start": "dist/client",
34925
+ astro: "dist for static, or dist/client for edge"
34899
34926
  }
34900
34927
  }
34901
34928
  },
@@ -34954,8 +34981,8 @@ var DETAIL_DEFINITIONS = FRAMEWORK_CONVERSION_DETAILS;
34954
34981
  var PLACEHOLDER = /\{\{([a-z]+)\}\}/gu;
34955
34982
  var PATH_PATTERN = "[A-Za-z0-9_@()[\\]./${}-]{1,240}";
34956
34983
  var SAFE_PATH = new RegExp(`^${PATH_PATTERN}$`, "u");
34957
- var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:[cm]?[jt]sx?)$/u;
34958
- var FRAMEWORK_CONFIG_NAME = /^(?:next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
34984
+ var MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:astro|[cm]?[jt]sx?)$/u;
34985
+ var FRAMEWORK_CONFIG_NAME = /^(?:astro|next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
34959
34986
  var MAXIMUM_DETAIL_LENGTH = 2e3;
34960
34987
  var DETAIL_PATTERNS = Object.freeze(Object.entries(DETAIL_DEFINITIONS).map(([code, definition]) => Object.freeze({
34961
34988
  code,
@@ -37142,14 +37169,25 @@ function parseProjectCreditBudget(value) {
37142
37169
  return input;
37143
37170
  }
37144
37171
  var parseCurrentIdentity = (value) => {
37145
- const input = exactRecord2(value, ["actor_id", "organization_ids"]);
37172
+ const input = exactRecord2(value, ["actor_id", "email", "organization_ids", "organizations"]);
37173
+ const email = input["email"];
37146
37174
  const organizationIds = input["organization_ids"];
37175
+ const organizations = input["organizations"];
37147
37176
  if (typeof input["actor_id"] !== "string" || input["actor_id"].length === 0 || input["actor_id"].length > 256 || [...input["actor_id"]].some((character) => {
37148
37177
  const codePoint = character.codePointAt(0) ?? 0;
37149
37178
  return codePoint <= 31 || codePoint === 127;
37150
- }) || !Array.isArray(organizationIds) || organizationIds.some((organizationId) => !isUlid(organizationId)) || new Set(organizationIds).size !== organizationIds.length) {
37179
+ }) || email !== null && (!boundedString(email, 3, 254) || !/^[^\s@]+@[^\s@.]+(?:\.[^\s@.]+)+$/u.test(email) || /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(email)) || !Array.isArray(organizationIds) || organizationIds.some((organizationId) => !isUlid(organizationId)) || new Set(organizationIds).size !== organizationIds.length || !Array.isArray(organizations) || organizations.length !== organizationIds.length) {
37151
37180
  throw new ResponseContractError();
37152
37181
  }
37182
+ organizations.forEach((organization, index) => {
37183
+ const entry = exactRecord2(organization, ["id", "name"]);
37184
+ if (entry["id"] !== organizationIds[index] || !boundedString(entry["name"], 1, 128) || entry["name"].trim() !== entry["name"] || [...entry["name"]].some((character) => {
37185
+ const codePoint = character.codePointAt(0) ?? 0;
37186
+ return codePoint <= 31 || codePoint === 127;
37187
+ })) {
37188
+ throw new ResponseContractError();
37189
+ }
37190
+ });
37153
37191
  return input;
37154
37192
  };
37155
37193
  var parseAccountProfile = (value) => {
@@ -40628,7 +40666,7 @@ function exact3(v, keys) {
40628
40666
  // apps/product-cli/package.json
40629
40667
  var package_default = {
40630
40668
  name: "@ohmyhost/product-cli",
40631
- version: "0.1.33",
40669
+ version: "0.1.35",
40632
40670
  private: true,
40633
40671
  ohmyhost: {
40634
40672
  deployment: "production",
@@ -40965,7 +41003,7 @@ var HELP_USAGE = Object.freeze({
40965
41003
  "project flag set": "ohmyhost project flag set --project ULID --enabled true|false --idempotency-key KEY --json (shows the small Powered by ohmyho.st flag on the production site; while it shows, this project's custom domain uses no domain credits and also works on Free, and each Paid period bought through Stripe adds 250 credits to the workspace, however many projects show the flag)",
40966
41004
  "project handle check": "ohmyhost project handle check --handle HANDLE --json (is this address free? answers with a reason and free alternatives; the address becomes HANDLE.check.omh.st)",
40967
41005
  "project handle set": "ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
40968
- "database compute set": "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
41006
+ "database compute set": "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n standard: Free 0.25 CU/1 GB, Paid 0.5 CU/2 GB. performance: Paid 1 CU/4 GB, 2.5x database compute credits per equal active minute. All profiles use the provider-default idle suspension.",
40969
41007
  "database compute get": "ohmyhost database compute get --project ULID [--environment dev|prod] --json",
40970
41008
  "database write": "ohmyhost database write --project ULID --environment dev|prod --statement-file PATH --idempotency-key KEY [--parameters-json JSON] --yes --json",
40971
41009
  "database query": "ohmyhost database query --project ULID --environment dev|prod --statement SQL [--parameters-json JSON] --json",
@@ -47136,9 +47174,14 @@ var import_yaml2 = __toESM(require_dist(), 1);
47136
47174
 
47137
47175
  // packages/contracts/dist/framework-family.js
47138
47176
  function classifyFrameworkFamily(dependencies) {
47177
+ const astro = dependencies.has("astro");
47139
47178
  const next = dependencies.has("next");
47140
47179
  const tanStack = dependencies.has("@tanstack/react-start");
47141
47180
  const vite = dependencies.has("vite");
47181
+ if (astro && (next || tanStack))
47182
+ return "ambiguous";
47183
+ if (astro)
47184
+ return "astro";
47142
47185
  if (next && (tanStack || vite))
47143
47186
  return "ambiguous";
47144
47187
  if (next)
@@ -47164,6 +47207,7 @@ var DEPENDENCY_FIELDS = Object.freeze({
47164
47207
  any: Object.freeze(["dependencies", "devDependencies", "optionalDependencies"])
47165
47208
  });
47166
47209
  var REQUIRED_DEPENDENCIES = Object.freeze({
47210
+ astro: Object.freeze([["astro", "build"]]),
47167
47211
  nextjs: Object.freeze([
47168
47212
  ["next", "runtime"],
47169
47213
  ["react", "runtime"],
@@ -47182,6 +47226,7 @@ var REQUIRED_DEPENDENCIES = Object.freeze({
47182
47226
  });
47183
47227
  var TANSTACK_EDGE_DEPENDENCY = Object.freeze([CLOUDFLARE_PLUGIN, "build"]);
47184
47228
  var BUILD_SCRIPT_FRAMEWORKS = Object.freeze({
47229
+ astro: "astro",
47185
47230
  nextjs: "nextjs",
47186
47231
  "vite-static": "vite",
47187
47232
  "tanstack-start": "tanstack-start"
@@ -47190,7 +47235,11 @@ var BUILD_OUTPUTS = Object.freeze({
47190
47235
  "vite-static": "dist",
47191
47236
  "tanstack-start": "dist/client"
47192
47237
  });
47193
- var REQUIRED_CONFIG_PATHS = Object.freeze({ nextjs: "next.config.ts", "tanstack-start": "vite.config.ts" });
47238
+ var REQUIRED_CONFIG_PATHS = Object.freeze({
47239
+ astro: "astro.config.mjs",
47240
+ nextjs: "next.config.ts",
47241
+ "tanstack-start": "vite.config.ts"
47242
+ });
47194
47243
  var POSTCSS_CONFIG_PATH = /^(?:postcss\.config\.(?:[cm]?js|json)|\.postcssrc\.(?:js|json))$/u;
47195
47244
  var TAILWIND_TURBOPACK_LOADER = "@tailwindcss/turbopack";
47196
47245
  var MAXIMUM_LOCKFILE_LENGTH = 5 * 1024 * 1024;
@@ -47252,7 +47301,7 @@ function viteCompanionRequired(configuration) {
47252
47301
  function frameworkVersionAdmissions(framework, manifest2, lockfile) {
47253
47302
  const admissions = [];
47254
47303
  let locked;
47255
- for (const [name, placement] of REQUIRED_DEPENDENCIES[framework]) {
47304
+ for (const [name, placement] of framework === "astro" && declaredDependency(manifest2, "@astrojs/cloudflare", "build") !== null ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"]] : REQUIRED_DEPENDENCIES[framework]) {
47256
47305
  if (!isFrameworkVersionPackage(name))
47257
47306
  continue;
47258
47307
  const declared = declaredDependency(manifest2, name, placement);
@@ -47440,14 +47489,14 @@ function sourceWithoutComments(source) {
47440
47489
  return result;
47441
47490
  }
47442
47491
  function isDatabaseContractModule(path2) {
47443
- return /\.(?:ts|tsx|js|jsx|mjs|mts)$/u.test(path2) && !/\.d\.[cm]?ts$/u.test(path2);
47492
+ return /\.(?:astro|ts|tsx|js|jsx|mjs|mts)$/u.test(path2) && !/\.d\.[cm]?ts$/u.test(path2);
47444
47493
  }
47445
47494
  function manifestFindings(input, versions) {
47446
47495
  const { configuration, framework, manifest: manifest2 } = input;
47447
47496
  const findings = [];
47448
47497
  if (framework !== "functions" && !isAdmittedFrameworkBuildScript(BUILD_SCRIPT_FRAMEWORKS[framework], ownValue(manifest2["scripts"], "build")))
47449
47498
  findings.push(diagnostic({ code: "build_script_unsupported", path: PACKAGE_MANIFEST2, framework }));
47450
- const required = framework === "tanstack-start" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY] : REQUIRED_DEPENDENCIES[framework];
47499
+ const required = framework === "astro" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"]] : framework === "tanstack-start" && configuration.runtime.mode === "edge" ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY] : REQUIRED_DEPENDENCIES[framework];
47451
47500
  for (const [name, placement] of required)
47452
47501
  if (declaredDependency(manifest2, name, placement) === null)
47453
47502
  findings.push(diagnostic({
@@ -47481,14 +47530,14 @@ function configurationFindings(input) {
47481
47530
  findings.push(diagnostic({ code: "runtime_mode_unsupported", path: REPOSITORY_CONFIG, framework }));
47482
47531
  if (build !== null && framework !== "nextjs" && build.ssg_cache_max_mib !== void 0)
47483
47532
  findings.push(diagnostic({ code: "build_option_unsupported", path: REPOSITORY_CONFIG }));
47484
- const output = BUILD_OUTPUTS[framework];
47533
+ const output = framework === "astro" ? configuration.runtime.mode === "edge" ? "dist/client" : "dist" : BUILD_OUTPUTS[framework];
47485
47534
  if (build !== null && output !== void 0 && build.output !== output)
47486
47535
  findings.push(diagnostic({ code: "build_output_unsupported", path: REPOSITORY_CONFIG, framework }));
47487
47536
  if (packageManager !== null && framework !== "nextjs" && (configuration.build.install !== packageManager.installCommand || build !== null && build.command !== packageManager.buildCommand))
47488
47537
  findings.push(diagnostic({ code: "build_command_mismatch", path: REPOSITORY_CONFIG }));
47489
47538
  if (framework === "vite-static" && viteCompanionRequired(configuration) && !files.includes(VITE_COMPANION_ENTRY_PATH))
47490
47539
  findings.push(diagnostic({ code: "companion_entry_required", path: VITE_COMPANION_ENTRY_PATH }));
47491
- if ((framework === "functions" || crons && (framework === "nextjs" || framework === "tanstack-start" && configuration.runtime.mode === "edge")) && !files.includes(WORKER_MODULE_PATH))
47540
+ if ((framework === "functions" || crons && (framework === "nextjs" || framework === "astro" && configuration.runtime.mode === "edge" || framework === "tanstack-start" && configuration.runtime.mode === "edge")) && !files.includes(WORKER_MODULE_PATH))
47492
47541
  findings.push(diagnostic({ code: "worker_module_required", path: WORKER_MODULE_PATH }));
47493
47542
  if (managedCustomerAuthAdmissionIssue(configuration) !== null)
47494
47543
  findings.push(diagnostic({ code: "managed_auth_database_required", path: REPOSITORY_CONFIG }));
@@ -47496,6 +47545,8 @@ function configurationFindings(input) {
47496
47545
  }
47497
47546
  function runtimeModeAdmitted(framework, configuration, crons) {
47498
47547
  const mode = configuration.runtime.mode;
47548
+ if (framework === "astro")
47549
+ return mode === "edge" || mode === "static" && !crons && configuration.storage === void 0 && configuration.database?.enabled !== true && configuration.mail?.enabled !== true && configuration.auth?.provider !== "better-auth";
47499
47550
  if (framework === "nextjs")
47500
47551
  return mode === "edge";
47501
47552
  if (framework === "vite-static")
@@ -47510,6 +47561,8 @@ function frameworkConfigFindings(input) {
47510
47561
  if (frameworkConfig === null || framework === "functions")
47511
47562
  return [];
47512
47563
  const { path: path2, text } = frameworkConfig;
47564
+ if (framework === "astro")
47565
+ return astroConfigFindings(text, path2, input.configuration.runtime.mode);
47513
47566
  if (framework === "nextjs")
47514
47567
  return nextConfigFindings(text, path2, hasPostcssConfiguration(input));
47515
47568
  if (framework === "vite-static")
@@ -47534,7 +47587,7 @@ function repositoryConfiguration(text) {
47534
47587
  function frameworkConfigPath(framework, files, findings) {
47535
47588
  if (framework === "functions")
47536
47589
  return null;
47537
- const path2 = admitted(findings, () => resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files));
47590
+ const path2 = admitted(findings, () => resolveFrameworkConfig(framework === "astro" ? "astro.config" : framework === "nextjs" ? "next.config" : "vite.config", files));
47538
47591
  if (framework === "nextjs")
47539
47592
  admitted(findings, () => resolveFrameworkConfig("open-next.config", files));
47540
47593
  const required = REQUIRED_CONFIG_PATHS[framework];
@@ -47836,6 +47889,15 @@ function hasNamedImport(tokens, name, moduleName) {
47836
47889
  function hasModuleImport(tokens, moduleName) {
47837
47890
  return tokens.some((token, index) => token.value === "from" && tokens[index + 1]?.kind === "string" && tokens[index + 1]?.value === moduleName);
47838
47891
  }
47892
+ function astroConfigFindings(text, path2, mode) {
47893
+ const lexed = lexFrameworkConfig(text, path2);
47894
+ if ("finding" in lexed)
47895
+ return [lexed.finding];
47896
+ const tokens = lexed.tokens;
47897
+ const hasPropertyKey = (items, name) => items.some((token, index) => token.value === name && items[index + 1]?.value === ":");
47898
+ const invalid6 = hasNonRootProperty(tokens, ["base"], ["", "/"]) || ["srcDir", "outDir", "serverEntry", "client", "server", "assetsPrefix"].some((name) => hasPropertyKey(tokens, name)) || (mode === "edge" ? !text.includes("@astrojs/cloudflare") || !hasProperty(tokens, "output", "server") || !hasProperty(tokens, "session", "false") || !hasProperty(tokens, "imageService", "passthrough") || hasPropertyKey(tokens, "cache") : text.includes("@astrojs/cloudflare") || hasPropertyKey(tokens, "adapter") || hasProperty(tokens, "output", "server"));
47899
+ return invalid6 ? [diagnostic({ code: "astro_config_unsupported", path: path2 })] : [];
47900
+ }
47839
47901
 
47840
47902
  // packages/contracts/dist/database-migration-errors.js
47841
47903
  var DatabaseMigrationAdmissionError = class extends Error {
@@ -48288,7 +48350,7 @@ async function initializeRepository(input) {
48288
48350
  const match = path2.match(/^supabase\/functions\/([^/]+)\//u);
48289
48351
  return match?.[1] === void 0 || match[1].startsWith("_") ? [] : [match[1]];
48290
48352
  }));
48291
- const apiRouteCount = applicationFiles.filter((path2) => /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path2)).length;
48353
+ const apiRouteCount = applicationFiles.filter((path2) => /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path2) || framework === "astro" && /^src\/pages\/.+\.(?:js|ts)$/u.test(path2) && !path2.endsWith(".d.ts")).length;
48292
48354
  const supabase = dependencies.has("@supabase/supabase-js") || dependencies.has("@supabase/ssr");
48293
48355
  const configuredDatabase = existingConfiguration?.database?.enabled === true;
48294
48356
  const databaseProvider = supabaseMigrationCount > 0 ? "supabase" : postgresMigrationCount > 0 || configuredDatabase ? "postgresql" : "none";
@@ -48328,14 +48390,14 @@ async function initializeRepository(input) {
48328
48390
  const targetBetterAuth = existingConfiguration?.auth?.provider === "better-auth";
48329
48391
  const targetMail = existingConfiguration?.mail?.enabled === true;
48330
48392
  const targetStorage = r2 || supabaseStorage;
48331
- const tanStackRuntime = framework !== "tanstack-start" ? null : targetStorage ? "edge" : proposedTanStackRuntime(frameworkConfig, dependencies);
48393
+ const tanStackRuntime = framework !== "tanstack-start" && framework !== "astro" ? null : targetStorage ? "edge" : framework === "astro" ? dependencies.has("@astrojs/cloudflare") ? "edge" : "static" : proposedTanStackRuntime(frameworkConfig, dependencies);
48332
48394
  const companionRequired = framework === "vite" && (existingConfiguration === null ? databaseProvider !== "none" || targetBetterAuth || targetMail || targetStorage || edgeFunctionNames.size > 0 || applicationFiles.includes(VITE_COMPANION_ENTRY_PATH) : viteCompanionRequired(existingConfiguration));
48333
48395
  const generatedConfigurationYaml = renderConfiguration({
48334
48396
  project,
48335
48397
  framework,
48336
48398
  packageManager,
48337
48399
  databaseProvider,
48338
- hasHealthcheck: applicationFiles.includes("src/app/api/health/route.ts") || applicationFiles.includes("app/api/health/route.ts"),
48400
+ hasHealthcheck: applicationFiles.includes("src/app/api/health/route.ts") || applicationFiles.includes("app/api/health/route.ts") || framework === "astro" && applicationFiles.some((path2) => /^src\/pages\/api\/health(?:\/index)?\.(?:js|ts)$/u.test(path2)),
48339
48401
  mailEnabled: targetMail,
48340
48402
  betterAuthEnabled: targetBetterAuth,
48341
48403
  storageEnabled: targetStorage,
@@ -48453,7 +48515,7 @@ function requiredCatalogBlocker(diagnostic2, applicationRoot) {
48453
48515
  async function workerModuleBlockers(context) {
48454
48516
  const { config, framework, files } = context;
48455
48517
  const mode = config?.runtime.mode;
48456
- const scheduled = (config?.functions?.crons.length ?? 0) > 0 && (mode === "edge" && (framework === "vite" || framework === "nextjs" || framework === "tanstack-start") || mode === "functions" && framework === "functions");
48518
+ const scheduled = (config?.functions?.crons.length ?? 0) > 0 && (mode === "edge" && (framework === "astro" || framework === "vite" || framework === "nextjs" || framework === "tanstack-start") || mode === "functions" && framework === "functions");
48457
48519
  const path2 = framework === "vite" ? VITE_COMPANION_ENTRY_PATH : WORKER_MODULE_PATH;
48458
48520
  if (framework !== "functions" && !scheduled || !files.includes(path2))
48459
48521
  return [];
@@ -48726,7 +48788,7 @@ function hasDirectSafeBuildCommand(manifest2, framework) {
48726
48788
  const scripts = manifest2["scripts"];
48727
48789
  if (!isRecord5(scripts) || typeof scripts["build"] !== "string")
48728
48790
  return false;
48729
- return (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") && isAdmittedFrameworkBuildScript(framework, scripts["build"]);
48791
+ return (framework === "astro" || framework === "nextjs" || framework === "vite" || framework === "tanstack-start") && isAdmittedFrameworkBuildScript(framework, scripts["build"]);
48730
48792
  }
48731
48793
  async function assertRepositoryRoot(root) {
48732
48794
  let information;
@@ -48832,11 +48894,11 @@ async function readResolvingLockfile(applicationDirectory, admission) {
48832
48894
  return text === null ? null : Object.freeze({ name: admission.lockfile, text });
48833
48895
  }
48834
48896
  async function readFrameworkConfig(applicationDirectory, files, framework) {
48835
- if (framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start")
48897
+ if (framework !== "astro" && framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start")
48836
48898
  return null;
48837
48899
  let path2;
48838
48900
  try {
48839
- path2 = resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files);
48901
+ path2 = resolveFrameworkConfig(framework === "astro" ? "astro.config" : framework === "nextjs" ? "next.config" : "vite.config", files);
48840
48902
  } catch (error) {
48841
48903
  if (error instanceof FrameworkAdmissionError)
48842
48904
  return null;
@@ -48902,7 +48964,7 @@ function renderConfiguration(input) {
48902
48964
  let output = "dist";
48903
48965
  if (input.framework === "nextjs")
48904
48966
  output = ".open-next/assets";
48905
- else if (input.framework === "tanstack-start")
48967
+ else if (input.framework === "tanstack-start" || input.framework === "astro" && input.tanStackRuntime === "edge")
48906
48968
  output = "dist/client";
48907
48969
  let mode = "auto";
48908
48970
  if (input.framework === "functions")
package/dist/index.js CHANGED
@@ -99,7 +99,7 @@ import {
99
99
  selectWorkspace,
100
100
  verifiedUserId,
101
101
  workspaceOf
102
- } from "./chunk-3RDHFA76.js";
102
+ } from "./chunk-ZQSLRNH6.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.33",
3
+ "version": "0.1.35",
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.33",
3
+ "version": "0.1.35",
4
4
  "private": true,
5
5
  "ohmyhost": {
6
6
  "deployment": "production",
@@ -399,7 +399,7 @@ const HELP_USAGE = Object.freeze({
399
399
  "ohmyhost project handle set --project ULID --handle HANDLE --if-match ETAG --idempotency-key KEY --json (moves the project to a free address; the old one stops working and anyone may claim it)",
400
400
  "database compute set":
401
401
  "ohmyhost database compute set --project ULID --environment dev|prod --profile standard|performance --idempotency-key KEY --yes [--wait] --json\n" +
402
- " standard: Free 0.25 CU/1 GB/60s idle, Paid 0.5 CU/2 GB/60s idle. performance: Paid 1 CU/4 GB/300s idle, 2.5x database compute credits per equal active minute; longer idle time also consumes credits.",
402
+ " standard: Free 0.25 CU/1 GB, Paid 0.5 CU/2 GB. performance: Paid 1 CU/4 GB, 2.5x database compute credits per equal active minute. All profiles use the provider-default idle suspension.",
403
403
  "database compute get":
404
404
  "ohmyhost database compute get --project ULID [--environment dev|prod] --json",
405
405
  "database write":
@@ -310,8 +310,12 @@ export async function initializeRepository(
310
310
  return match?.[1] === undefined || match[1].startsWith("_") ? [] : [match[1]];
311
311
  }),
312
312
  );
313
- const apiRouteCount = applicationFiles.filter((path) =>
314
- /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path),
313
+ const apiRouteCount = applicationFiles.filter(
314
+ (path) =>
315
+ /^(?:src\/)?app\/api\/.+\/route\.(?:js|jsx|ts|tsx)$/u.test(path) ||
316
+ (framework === "astro" &&
317
+ /^src\/pages\/.+\.(?:js|ts)$/u.test(path) &&
318
+ !path.endsWith(".d.ts")),
315
319
  ).length;
316
320
  const supabase = dependencies.has("@supabase/supabase-js") || dependencies.has("@supabase/ssr");
317
321
  const configuredDatabase = existingConfiguration?.database?.enabled === true;
@@ -368,11 +372,15 @@ export async function initializeRepository(
368
372
  const targetStorage = r2 || supabaseStorage;
369
373
  // TanStack Start binds storage only on edge, as a Vite app does through its companion.
370
374
  const tanStackRuntime =
371
- framework !== "tanstack-start"
375
+ framework !== "tanstack-start" && framework !== "astro"
372
376
  ? null
373
377
  : targetStorage
374
378
  ? "edge"
375
- : proposedTanStackRuntime(frameworkConfig, dependencies);
379
+ : framework === "astro"
380
+ ? dependencies.has("@astrojs/cloudflare")
381
+ ? "edge"
382
+ : "static"
383
+ : proposedTanStackRuntime(frameworkConfig, dependencies);
376
384
  const companionRequired =
377
385
  framework === "vite" &&
378
386
  (existingConfiguration === null
@@ -390,7 +398,11 @@ export async function initializeRepository(
390
398
  databaseProvider,
391
399
  hasHealthcheck:
392
400
  applicationFiles.includes("src/app/api/health/route.ts") ||
393
- applicationFiles.includes("app/api/health/route.ts"),
401
+ applicationFiles.includes("app/api/health/route.ts") ||
402
+ (framework === "astro" &&
403
+ applicationFiles.some((path) =>
404
+ /^src\/pages\/api\/health(?:\/index)?\.(?:js|ts)$/u.test(path),
405
+ )),
394
406
  mailEnabled: targetMail,
395
407
  betterAuthEnabled: targetBetterAuth,
396
408
  storageEnabled: targetStorage,
@@ -596,7 +608,10 @@ async function workerModuleBlockers(context: SourceCheckContext): Promise<Reposi
596
608
  const scheduled =
597
609
  (config?.functions?.crons.length ?? 0) > 0 &&
598
610
  ((mode === "edge" &&
599
- (framework === "vite" || framework === "nextjs" || framework === "tanstack-start")) ||
611
+ (framework === "astro" ||
612
+ framework === "vite" ||
613
+ framework === "nextjs" ||
614
+ framework === "tanstack-start")) ||
600
615
  (mode === "functions" && framework === "functions"));
601
616
  const path = framework === "vite" ? VITE_COMPANION_ENTRY_PATH : WORKER_MODULE_PATH;
602
617
  if ((framework !== "functions" && !scheduled) || !files.includes(path)) return [];
@@ -989,7 +1004,10 @@ function hasDirectSafeBuildCommand(
989
1004
  const scripts = manifest["scripts"];
990
1005
  if (!isRecord(scripts) || typeof scripts["build"] !== "string") return false;
991
1006
  return (
992
- (framework === "nextjs" || framework === "vite" || framework === "tanstack-start") &&
1007
+ (framework === "astro" ||
1008
+ framework === "nextjs" ||
1009
+ framework === "vite" ||
1010
+ framework === "tanstack-start") &&
993
1011
  isAdmittedFrameworkBuildScript(framework, scripts["build"])
994
1012
  );
995
1013
  }
@@ -1144,10 +1162,23 @@ async function readFrameworkConfig(
1144
1162
  files: readonly string[],
1145
1163
  framework: RepositoryFramework,
1146
1164
  ): Promise<FrameworkConfigText | null> {
1147
- if (framework !== "nextjs" && framework !== "vite" && framework !== "tanstack-start") return null;
1165
+ if (
1166
+ framework !== "astro" &&
1167
+ framework !== "nextjs" &&
1168
+ framework !== "vite" &&
1169
+ framework !== "tanstack-start"
1170
+ )
1171
+ return null;
1148
1172
  let path: string | null;
1149
1173
  try {
1150
- path = resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files);
1174
+ path = resolveFrameworkConfig(
1175
+ framework === "astro"
1176
+ ? "astro.config"
1177
+ : framework === "nextjs"
1178
+ ? "next.config"
1179
+ : "vite.config",
1180
+ files,
1181
+ );
1151
1182
  } catch (error) {
1152
1183
  // The shared rules name the ambiguous variants; none of them is judged.
1153
1184
  if (error instanceof FrameworkAdmissionError) return null;
@@ -1266,7 +1297,11 @@ function renderConfiguration(input: {
1266
1297
 
1267
1298
  let output = "dist";
1268
1299
  if (input.framework === "nextjs") output = ".open-next/assets";
1269
- else if (input.framework === "tanstack-start") output = "dist/client";
1300
+ else if (
1301
+ input.framework === "tanstack-start" ||
1302
+ (input.framework === "astro" && input.tanStackRuntime === "edge")
1303
+ )
1304
+ output = "dist/client";
1270
1305
 
1271
1306
  let mode = "auto";
1272
1307
  if (input.framework === "functions") mode = "functions";
@@ -63,7 +63,9 @@ export interface SafeProblem {
63
63
 
64
64
  export interface PublicCurrentIdentity {
65
65
  readonly actor_id: string;
66
+ readonly email: string | null;
66
67
  readonly organization_ids: readonly string[];
68
+ readonly organizations: readonly { readonly id: string; readonly name: string }[];
67
69
  }
68
70
 
69
71
  /** One workspace the signed-in user belongs to, with the provider identity a selection needs. */
@@ -635,8 +637,10 @@ export function parseProjectCreditBudget(value: unknown): ProjectCreditBudget {
635
637
  }
636
638
 
637
639
  export const parseCurrentIdentity = (value: unknown): PublicCurrentIdentity => {
638
- const input = exactRecord(value, ["actor_id", "organization_ids"]);
640
+ const input = exactRecord(value, ["actor_id", "email", "organization_ids", "organizations"]);
641
+ const email = input["email"];
639
642
  const organizationIds = input["organization_ids"];
643
+ const organizations = input["organizations"];
640
644
  if (
641
645
  typeof input["actor_id"] !== "string" ||
642
646
  input["actor_id"].length === 0 ||
@@ -645,12 +649,32 @@ export const parseCurrentIdentity = (value: unknown): PublicCurrentIdentity => {
645
649
  const codePoint = character.codePointAt(0) ?? 0;
646
650
  return codePoint <= 31 || codePoint === 127;
647
651
  }) ||
652
+ (email !== null &&
653
+ (!boundedString(email, 3, 254) ||
654
+ !/^[^\s@]+@[^\s@.]+(?:\.[^\s@.]+)+$/u.test(email) ||
655
+ /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(email))) ||
648
656
  !Array.isArray(organizationIds) ||
649
657
  organizationIds.some((organizationId) => !isUlid(organizationId)) ||
650
- new Set(organizationIds).size !== organizationIds.length
658
+ new Set(organizationIds).size !== organizationIds.length ||
659
+ !Array.isArray(organizations) ||
660
+ organizations.length !== organizationIds.length
651
661
  ) {
652
662
  throw new ResponseContractError();
653
663
  }
664
+ organizations.forEach((organization: unknown, index) => {
665
+ const entry = exactRecord(organization, ["id", "name"]);
666
+ if (
667
+ entry["id"] !== organizationIds[index] ||
668
+ !boundedString(entry["name"], 1, 128) ||
669
+ entry["name"].trim() !== entry["name"] ||
670
+ [...entry["name"]].some((character) => {
671
+ const codePoint = character.codePointAt(0) ?? 0;
672
+ return codePoint <= 31 || codePoint === 127;
673
+ })
674
+ ) {
675
+ throw new ResponseContractError();
676
+ }
677
+ });
654
678
  return input as unknown as PublicCurrentIdentity;
655
679
  };
656
680
 
@@ -19,7 +19,11 @@ export type LockfileName =
19
19
  | "package-lock.json"
20
20
  | "pnpm-lock.yaml"
21
21
  | "yarn.lock";
22
- export type FrameworkConfigBasename = "next.config" | "open-next.config" | "vite.config";
22
+ export type FrameworkConfigBasename =
23
+ | "astro.config"
24
+ | "next.config"
25
+ | "open-next.config"
26
+ | "vite.config";
23
27
 
24
28
  export type FrameworkAdmissionReason =
25
29
  | "ambiguous_config"
@@ -311,11 +315,15 @@ interface DetailDefinition {
311
315
  }
312
316
 
313
317
  const FRAMEWORK_VERSION_PACKAGES = Object.freeze([
318
+ "astro",
319
+ "@astrojs/cloudflare",
314
320
  "next",
315
321
  "vite",
316
322
  "@tanstack/react-start",
317
323
  ] as const satisfies readonly FrameworkVersionPackage[]);
318
324
  const FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
325
+ "astro",
326
+ "@astrojs/cloudflare",
319
327
  "next",
320
328
  "react",
321
329
  "react-dom",
@@ -326,7 +334,12 @@ const FRAMEWORK_DEPENDENCY_PACKAGES = Object.freeze([
326
334
  "@cloudflare/vite-plugin",
327
335
  ] as const);
328
336
  const REACT_PACKAGES = Object.freeze(["react", "react-dom"] as const);
329
- const BUILD_FRAMEWORKS = Object.freeze(["nextjs", "vite-static", "tanstack-start"] as const);
337
+ const BUILD_FRAMEWORKS = Object.freeze([
338
+ "astro",
339
+ "nextjs",
340
+ "vite-static",
341
+ "tanstack-start",
342
+ ] as const);
330
343
 
331
344
  const PUSH_AND_PLAN = "Commit and push the change, then plan the new commit.";
332
345
  const VITE_BUILD_SCRIPT =
@@ -335,6 +348,16 @@ const VITE_BUILD_SCRIPT =
335
348
  // Each sentence names the file, the rule and the exact change. Render and parse both derive from
336
349
  // the template, so a client maps a detail back to exactly one typed diagnostic.
337
350
  const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
351
+ astro_template_import_unsupported: {
352
+ paths: ["module"],
353
+ template:
354
+ '"{{path}}" uses a dynamic import, import.meta or require in an Astro template. Move server imports into frontmatter and dynamic browser imports into a normal statically imported browser module, then commit and push the change and plan the new commit.',
355
+ },
356
+ astro_config_unsupported: {
357
+ paths: ["framework_config"],
358
+ template:
359
+ '"{{path}}" must use the standard Astro output directories and root base. For edge use output: "server", @astrojs/cloudflare with imageService: "passthrough", and session: false. Static hosting must omit a server adapter. Additional Cloudflare bindings, custom sessions and provider cache are not supported. Commit and push the change, then plan the new commit.',
360
+ },
338
361
  managed_auth_database_required: {
339
362
  paths: ["repository_config"],
340
363
  template: managedCustomerAuthDatabaseMessage("{{path}}"),
@@ -417,6 +440,7 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
417
440
  'exactly "next build" or "next build --webpack"; ohmyho.st adds the OpenNext build itself',
418
441
  "vite-static": VITE_BUILD_SCRIPT,
419
442
  "tanstack-start": VITE_BUILD_SCRIPT,
443
+ astro: 'exactly "astro build"',
420
444
  },
421
445
  },
422
446
  },
@@ -435,12 +459,12 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
435
459
  },
436
460
  framework_unsupported: {
437
461
  paths: ["manifest"],
438
- template: `"{{path}}" declares none of next, vite or @tanstack/react-start. Add the app's framework to dependencies, or set runtime.mode: functions in ohmyhost.yaml and add src/ohmyhost/worker.ts. ${PUSH_AND_PLAN}`,
462
+ template: `"{{path}}" declares none of astro, next, vite or @tanstack/react-start. Add the app's framework to dependencies, or set runtime.mode: functions in ohmyhost.yaml and add src/ohmyhost/worker.ts. ${PUSH_AND_PLAN}`,
439
463
  },
440
464
  framework_ambiguous: {
441
465
  paths: ["manifest"],
442
466
  template:
443
- '"{{path}}" declares next together with vite or @tanstack/react-start, so ohmyho.st cannot choose one framework. Remove the packages of the framework this application does not use, commit and push the change, then plan the new commit.',
467
+ '"{{path}}" declares competing application framework packages, so ohmyho.st cannot choose one framework. Remove the packages of the framework this application does not use, commit and push the change, then plan the new commit.',
444
468
  },
445
469
  lockfile_missing: {
446
470
  paths: ["manifest"],
@@ -487,6 +511,7 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
487
511
  "vite-static":
488
512
  "runtime.mode: edge with src/ohmyhost/companion.ts when database, auth, mail, storage or functions.crons is set, and static otherwise",
489
513
  "tanstack-start": "runtime.mode: edge, or static without functions.crons and storage",
514
+ astro: "runtime.mode: static for prerendered sites, or edge with the Cloudflare adapter",
490
515
  },
491
516
  },
492
517
  },
@@ -502,6 +527,7 @@ const FRAMEWORK_CONVERSION_DETAILS = Object.freeze({
502
527
  nextjs: ".open-next/assets",
503
528
  "vite-static": "dist",
504
529
  "tanstack-start": "dist/client",
530
+ astro: "dist for static, or dist/client for edge",
505
531
  },
506
532
  },
507
533
  },
@@ -585,8 +611,8 @@ const DETAIL_DEFINITIONS: Readonly<Record<FrameworkConversionDiagnosticCode, Det
585
611
  const PLACEHOLDER = /\{\{([a-z]+)\}\}/gu;
586
612
  const PATH_PATTERN = "[A-Za-z0-9_@()[\\]./${}-]{1,240}";
587
613
  const SAFE_PATH = new RegExp(`^${PATH_PATTERN}$`, "u");
588
- const MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:[cm]?[jt]sx?)$/u;
589
- const FRAMEWORK_CONFIG_NAME = /^(?:next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
614
+ const MODULE_PATH = /^[A-Za-z0-9_@()[\]./${}-]+\.(?:astro|[cm]?[jt]sx?)$/u;
615
+ const FRAMEWORK_CONFIG_NAME = /^(?:astro|next|open-next|vite)\.config\.(?:js|mjs|ts)$/u;
590
616
  // The public problem detail bound; every rendered sentence stays far below it.
591
617
  const MAXIMUM_DETAIL_LENGTH = 2_000;
592
618
  const DETAIL_PATTERNS = Object.freeze(
@@ -1,7 +1,7 @@
1
1
  export const PINNED_BUN_VERSION = "1.2.22";
2
2
 
3
3
  /**
4
- * @typedef {"next" | "vite" | "@tanstack/react-start"} FrameworkVersionPackage
4
+ * @typedef {"next" | "vite" | "astro" | "@astrojs/cloudflare" | "@tanstack/react-start"} FrameworkVersionPackage
5
5
  * @typedef {Readonly<{
6
6
  * floors: readonly string[];
7
7
  * denied: readonly (readonly [string, string | null])[];
@@ -22,6 +22,11 @@ export const PINNED_BUN_VERSION = "1.2.22";
22
22
  * @type {FrameworkVersionPolicy}
23
23
  */
24
24
  export const FRAMEWORK_VERSION_POLICY = Object.freeze({
25
+ astro: Object.freeze({ floors: Object.freeze(["7.4.0"]), denied: Object.freeze([]) }),
26
+ "@astrojs/cloudflare": Object.freeze({
27
+ floors: Object.freeze(["14.4.0"]),
28
+ denied: Object.freeze([]),
29
+ }),
25
30
  next: Object.freeze({ floors: Object.freeze(["15.5.27", "16.3.8"]), denied: Object.freeze([]) }),
26
31
  vite: Object.freeze({ floors: Object.freeze(["5.4.6"]), denied: Object.freeze([]) }),
27
32
  "@tanstack/react-start": Object.freeze({
@@ -164,12 +169,13 @@ const TYPESCRIPT_BUILD_STAGE = /^tsc(?:[ \t]+(?:--noEmit|-b|--build))?$/u;
164
169
 
165
170
  /**
166
171
  * The same source is consumed by local admission, framework adapters and the build sandbox.
167
- * @param {"nextjs" | "tanstack-start" | "vite"} framework
172
+ * @param {"astro" | "nextjs" | "tanstack-start" | "vite"} framework
168
173
  * @param {unknown} script
169
174
  * @returns {boolean}
170
175
  */
171
176
  export function isAdmittedFrameworkBuildScript(framework, script) {
172
177
  if (typeof script !== "string") return false;
178
+ if (framework === "astro") return script === "astro build";
173
179
  if (framework === "nextjs") return script === "next build" || script === "next build --webpack";
174
180
  if (framework !== "vite" && framework !== "tanstack-start") return false;
175
181
  const stages = script.trim().split(/[ \t]*&&[ \t]*/u);
@@ -1,4 +1,5 @@
1
1
  export type FrameworkFamilyClassification =
2
+ | "astro"
2
3
  | "ambiguous"
3
4
  | "nextjs"
4
5
  | "tanstack-start"
@@ -8,9 +9,12 @@ export type FrameworkFamilyClassification =
8
9
  export function classifyFrameworkFamily(
9
10
  dependencies: ReadonlySet<string>,
10
11
  ): FrameworkFamilyClassification {
12
+ const astro = dependencies.has("astro");
11
13
  const next = dependencies.has("next");
12
14
  const tanStack = dependencies.has("@tanstack/react-start");
13
15
  const vite = dependencies.has("vite");
16
+ if (astro && (next || tanStack)) return "ambiguous";
17
+ if (astro) return "astro";
14
18
  if (next && (tanStack || vite)) return "ambiguous";
15
19
  if (next) return "nextjs";
16
20
  if (tanStack) return "tanstack-start";
@@ -40,7 +40,12 @@ export const WORKER_MODULE_PATH = "src/ohmyhost/worker.ts" as const;
40
40
  /** The Worker module of a Vite application with a server runtime. */
41
41
  export const VITE_COMPANION_ENTRY_PATH = "src/ohmyhost/companion.ts" as const;
42
42
 
43
- export type FrameworkSourceFramework = "functions" | "nextjs" | "tanstack-start" | "vite-static";
43
+ export type FrameworkSourceFramework =
44
+ | "astro"
45
+ | "functions"
46
+ | "nextjs"
47
+ | "tanstack-start"
48
+ | "vite-static";
44
49
  export type PackageManifest = Readonly<Record<string, unknown>>;
45
50
  /**
46
51
  * Where package.json may declare a dependency: `runtime` only in dependencies, `build` also in
@@ -91,6 +96,7 @@ const DEPENDENCY_FIELDS = Object.freeze({
91
96
  // Next.js and TanStack Start load their runtime packages in the server bundle, so package.json
92
97
  // declares them in dependencies; Vite and its plugins only build, so devDependencies suffice.
93
98
  const REQUIRED_DEPENDENCIES = Object.freeze({
99
+ astro: Object.freeze([["astro", "build"]] as const),
94
100
  nextjs: Object.freeze([
95
101
  ["next", "runtime"],
96
102
  ["react", "runtime"],
@@ -109,6 +115,7 @@ const REQUIRED_DEPENDENCIES = Object.freeze({
109
115
  } satisfies Record<FrameworkSourceFramework, readonly (readonly [string, DependencyPlacement])[]>);
110
116
  const TANSTACK_EDGE_DEPENDENCY = Object.freeze([CLOUDFLARE_PLUGIN, "build"] as const);
111
117
  const BUILD_SCRIPT_FRAMEWORKS = Object.freeze({
118
+ astro: "astro",
112
119
  nextjs: "nextjs",
113
120
  "vite-static": "vite",
114
121
  "tanstack-start": "tanstack-start",
@@ -118,7 +125,11 @@ const BUILD_OUTPUTS: Readonly<Partial<Record<FrameworkSourceFramework, string>>>
118
125
  "tanstack-start": "dist/client",
119
126
  });
120
127
  const REQUIRED_CONFIG_PATHS: Readonly<Partial<Record<FrameworkSourceFramework, string>>> =
121
- Object.freeze({ nextjs: "next.config.ts", "tanstack-start": "vite.config.ts" });
128
+ Object.freeze({
129
+ astro: "astro.config.mjs",
130
+ nextjs: "next.config.ts",
131
+ "tanstack-start": "vite.config.ts",
132
+ });
122
133
  // The PostCSS configuration the Next.js Webpack build reads: package.json "postcss" or one of these
123
134
  // files at the application root. It never reads a TypeScript one such as postcss.config.ts.
124
135
  const POSTCSS_CONFIG_PATH = /^(?:postcss\.config\.(?:[cm]?js|json)|\.postcssrc\.(?:js|json))$/u;
@@ -214,7 +225,10 @@ export function frameworkVersionAdmissions(
214
225
  ): readonly FrameworkVersionAdmission[] {
215
226
  const admissions: FrameworkVersionAdmission[] = [];
216
227
  let locked: ((packageName: string) => string | null) | undefined;
217
- for (const [name, placement] of REQUIRED_DEPENDENCIES[framework]) {
228
+ for (const [name, placement] of framework === "astro" &&
229
+ declaredDependency(manifest, "@astrojs/cloudflare", "build") !== null
230
+ ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"] as const]
231
+ : REQUIRED_DEPENDENCIES[framework]) {
218
232
  if (!isFrameworkVersionPackage(name)) continue;
219
233
  const declared = declaredDependency(manifest, name, placement);
220
234
  if (declared === null) continue;
@@ -504,7 +518,7 @@ export function sourceWithoutComments(source: string): string {
504
518
  * extensions planning reads, without TypeScript declaration files, which never run.
505
519
  */
506
520
  export function isDatabaseContractModule(path: string): boolean {
507
- return /\.(?:ts|tsx|js|jsx|mjs|mts)$/u.test(path) && !/\.d\.[cm]?ts$/u.test(path);
521
+ return /\.(?:astro|ts|tsx|js|jsx|mjs|mts)$/u.test(path) && !/\.d\.[cm]?ts$/u.test(path);
508
522
  }
509
523
 
510
524
  function manifestFindings(
@@ -524,9 +538,11 @@ function manifestFindings(
524
538
  diagnostic({ code: "build_script_unsupported", path: PACKAGE_MANIFEST, framework }),
525
539
  );
526
540
  const required =
527
- framework === "tanstack-start" && configuration.runtime.mode === "edge"
528
- ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY]
529
- : REQUIRED_DEPENDENCIES[framework];
541
+ framework === "astro" && configuration.runtime.mode === "edge"
542
+ ? [...REQUIRED_DEPENDENCIES.astro, ["@astrojs/cloudflare", "build"] as const]
543
+ : framework === "tanstack-start" && configuration.runtime.mode === "edge"
544
+ ? [...REQUIRED_DEPENDENCIES[framework], TANSTACK_EDGE_DEPENDENCY]
545
+ : REQUIRED_DEPENDENCIES[framework];
530
546
  for (const [name, placement] of required)
531
547
  if (declaredDependency(manifest, name, placement) === null)
532
548
  findings.push(
@@ -567,7 +583,12 @@ function configurationFindings(input: FrameworkSourceRuleInput): FrameworkConver
567
583
  );
568
584
  if (build !== null && framework !== "nextjs" && build.ssg_cache_max_mib !== undefined)
569
585
  findings.push(diagnostic({ code: "build_option_unsupported", path: REPOSITORY_CONFIG }));
570
- const output = BUILD_OUTPUTS[framework];
586
+ const output =
587
+ framework === "astro"
588
+ ? configuration.runtime.mode === "edge"
589
+ ? "dist/client"
590
+ : "dist"
591
+ : BUILD_OUTPUTS[framework];
571
592
  if (build !== null && output !== undefined && build.output !== output)
572
593
  findings.push(
573
594
  diagnostic({ code: "build_output_unsupported", path: REPOSITORY_CONFIG, framework }),
@@ -592,6 +613,7 @@ function configurationFindings(input: FrameworkSourceRuleInput): FrameworkConver
592
613
  (framework === "functions" ||
593
614
  (crons &&
594
615
  (framework === "nextjs" ||
616
+ (framework === "astro" && configuration.runtime.mode === "edge") ||
595
617
  (framework === "tanstack-start" && configuration.runtime.mode === "edge")))) &&
596
618
  !files.includes(WORKER_MODULE_PATH)
597
619
  )
@@ -607,6 +629,16 @@ function runtimeModeAdmitted(
607
629
  crons: boolean,
608
630
  ): boolean {
609
631
  const mode = configuration.runtime.mode;
632
+ if (framework === "astro")
633
+ return (
634
+ mode === "edge" ||
635
+ (mode === "static" &&
636
+ !crons &&
637
+ configuration.storage === undefined &&
638
+ configuration.database?.enabled !== true &&
639
+ configuration.mail?.enabled !== true &&
640
+ configuration.auth?.provider !== "better-auth")
641
+ );
610
642
  if (framework === "nextjs") return mode === "edge";
611
643
  if (framework === "vite-static")
612
644
  return mode === (viteCompanionRequired(configuration) ? "edge" : "static");
@@ -629,6 +661,8 @@ function frameworkConfigFindings(
629
661
  const { frameworkConfig, framework } = input;
630
662
  if (frameworkConfig === null || framework === "functions") return [];
631
663
  const { path, text } = frameworkConfig;
664
+ if (framework === "astro")
665
+ return astroConfigFindings(text, path, input.configuration.runtime.mode);
632
666
  if (framework === "nextjs") return nextConfigFindings(text, path, hasPostcssConfiguration(input));
633
667
  if (framework === "vite-static") return viteConfigFindings(text, path);
634
668
  const mode = input.configuration.runtime.mode;
@@ -660,7 +694,14 @@ function frameworkConfigPath(
660
694
  ): string | null {
661
695
  if (framework === "functions") return null;
662
696
  const path = admitted(findings, () =>
663
- resolveFrameworkConfig(framework === "nextjs" ? "next.config" : "vite.config", files),
697
+ resolveFrameworkConfig(
698
+ framework === "astro"
699
+ ? "astro.config"
700
+ : framework === "nextjs"
701
+ ? "next.config"
702
+ : "vite.config",
703
+ files,
704
+ ),
664
705
  );
665
706
  if (framework === "nextjs")
666
707
  admitted(findings, () => resolveFrameworkConfig("open-next.config", files));
@@ -1116,3 +1157,31 @@ function hasModuleImport(tokens: readonly ConfigToken[], moduleName: string): bo
1116
1157
  tokens[index + 1]?.value === moduleName,
1117
1158
  );
1118
1159
  }
1160
+
1161
+ /** Astro's build validates executable config; admission rejects known incompatible contracts. */
1162
+ function astroConfigFindings(
1163
+ text: string,
1164
+ path: string,
1165
+ mode: string,
1166
+ ): readonly FrameworkConversionDiagnostic[] {
1167
+ const lexed = lexFrameworkConfig(text, path);
1168
+ if ("finding" in lexed) return [lexed.finding];
1169
+ const tokens = lexed.tokens;
1170
+ const hasPropertyKey = (items: readonly ConfigToken[], name: string) =>
1171
+ items.some((token, index) => token.value === name && items[index + 1]?.value === ":");
1172
+ const invalid =
1173
+ hasNonRootProperty(tokens, ["base"], ["", "/"]) ||
1174
+ ["srcDir", "outDir", "serverEntry", "client", "server", "assetsPrefix"].some((name) =>
1175
+ hasPropertyKey(tokens, name),
1176
+ ) ||
1177
+ (mode === "edge"
1178
+ ? !text.includes("@astrojs/cloudflare") ||
1179
+ !hasProperty(tokens, "output", "server") ||
1180
+ !hasProperty(tokens, "session", "false") ||
1181
+ !hasProperty(tokens, "imageService", "passthrough") ||
1182
+ hasPropertyKey(tokens, "cache")
1183
+ : text.includes("@astrojs/cloudflare") ||
1184
+ hasPropertyKey(tokens, "adapter") ||
1185
+ hasProperty(tokens, "output", "server"));
1186
+ return invalid ? [diagnostic({ code: "astro_config_unsupported", path })] : [];
1187
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohmyhost/sdk-ts",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -945,7 +945,7 @@ export const completeCloudflareDnsAuthorization = <ThrowOnError extends boolean
945
945
  /**
946
946
  * Get the current authenticated identity
947
947
  *
948
- * Returns the stable ohmyhost actor and internal organization identifiers derived from the bearer credential.
948
+ * Returns the stable ohmyhost actor, the current user's or API-key owner's email, and the credential's organization scope with current display names. Machine-to-machine credentials have a null email. Organizations match organization_ids in the same order.
949
949
  */
950
950
  export const getCurrentIdentity = <ThrowOnError extends boolean = true>(
951
951
  parameters?: {
@@ -1148,11 +1148,25 @@ export type OrganizationAccount = {
1148
1148
  };
1149
1149
 
1150
1150
  /**
1151
- * Minimal customer-visible identity and organization scope for authenticated agent clients.
1151
+ * Customer-visible current account and credential organization scope for authenticated agent clients.
1152
1152
  */
1153
1153
  export type CurrentIdentity = {
1154
1154
  actor_id: string;
1155
+ /**
1156
+ * Email of the current user or user API-key owner; null for machine-to-machine credentials.
1157
+ */
1158
+ email: string | null;
1155
1159
  organization_ids: Array<Ulid>;
1160
+ /**
1161
+ * Current names for exactly the organization_ids scope, in the same order.
1162
+ */
1163
+ organizations: Array<{
1164
+ id: Ulid;
1165
+ /**
1166
+ * Current organization display name without surrounding whitespace or ASCII control characters.
1167
+ */
1168
+ name: string;
1169
+ }>;
1156
1170
  };
1157
1171
 
1158
1172
  export type Project = {