@auersperg/flare 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/{chunk-5tjevcsj.js → chunk-r3ceakrn.js} +212 -20
  3. package/dist/cli/flare.js +9 -7
  4. package/dist/platform/admin.js +1 -1
  5. package/dist/platform/dashboard/_astro/auth-view.UTGGCEHc.js +1 -0
  6. package/dist/platform/dashboard/_astro/cli-approval.BZAWmpiR.js +1 -0
  7. package/dist/platform/dashboard/_astro/client.Bcl1NrNO.js +1 -0
  8. package/dist/platform/dashboard/_astro/client.BeKNsjPN.js +9 -0
  9. package/dist/platform/dashboard/_astro/common.DPoFQu44.js +1 -0
  10. package/dist/platform/dashboard/_astro/data-view.BgYyTCMN.js +1 -0
  11. package/dist/platform/dashboard/_astro/functions-view.PUkng3T1.js +1 -0
  12. package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.BEuO3H0E.js +4 -0
  13. package/dist/platform/dashboard/_astro/index.lobU7uK5.css +2 -0
  14. package/dist/platform/dashboard/_astro/input-group.CJxPRHej.js +1 -0
  15. package/dist/platform/dashboard/_astro/sdk-view.COmbEZrF.js +1 -0
  16. package/dist/platform/dashboard/_astro/services-view.C34dy0dk.js +1 -0
  17. package/dist/platform/dashboard/_astro/settings-view.IuD2r5JW.js +3 -0
  18. package/dist/platform/dashboard/index.html +1 -1
  19. package/dist/platform/manifest.json +46 -81
  20. package/dist/platform/runtime.js +1 -1
  21. package/package.json +1 -1
  22. package/dist/platform/dashboard/_astro/auth-view.CuxQ12Fa.js +0 -1
  23. package/dist/platform/dashboard/_astro/badge.DwKWvVfO.js +0 -1
  24. package/dist/platform/dashboard/_astro/card.Bdqmram9.js +0 -1
  25. package/dist/platform/dashboard/_astro/cli-approval.VKPc8wYS.js +0 -1
  26. package/dist/platform/dashboard/_astro/client.DYdPL2z4.js +0 -9
  27. package/dist/platform/dashboard/_astro/common.HrRZXwD9.js +0 -1
  28. package/dist/platform/dashboard/_astro/dashboard.CH594XTw.js +0 -3
  29. package/dist/platform/dashboard/_astro/data-view.CcyUYvId.js +0 -1
  30. package/dist/platform/dashboard/_astro/function-badge.MlTRYkqM.js +0 -1
  31. package/dist/platform/dashboard/_astro/functions-view.C8JWdNAh.js +0 -1
  32. package/dist/platform/dashboard/_astro/index.DiiMDa7R.css +0 -2
  33. package/dist/platform/dashboard/_astro/input-group.3twGpQ77.js +0 -1
  34. package/dist/platform/dashboard/_astro/react-dom.DwX89IcG.js +0 -1
  35. package/dist/platform/dashboard/_astro/refresh-cw.B0Cj85jJ.js +0 -1
  36. package/dist/platform/dashboard/_astro/sdk-view.CRFkbEyS.js +0 -1
  37. package/dist/platform/dashboard/_astro/services-view.B2suYnaG.js +0 -1
  38. package/dist/platform/dashboard/_astro/settings-view.BFP47ySK.js +0 -3
  39. package/dist/platform/dashboard/_astro/status-badge.BV1dzbX9.js +0 -1
  40. package/dist/platform/dashboard/_astro/table-guide.DzBDP6fC.js +0 -2
  41. package/dist/platform/dashboard/_astro/tabs.GRO5rCrK.js +0 -1
package/README.md CHANGED
@@ -63,7 +63,7 @@ flare deploy --env dev --release release
63
63
 
64
64
  Immutable output contains facet/action modules, `schema.json`, manifest, checksums, generated TypeScript API and OpenAPI 3.1. Only ABI 4 is accepted. No old `tables` declarations, `--migrations` flag or Drizzle pipeline.
65
65
 
66
- Browser approval is protected by online Access/owner authorization and same-origin CSRF. Project-scoped credentials stay outside source/frontend env. Dev publishes to an already provisioned platform. Source 0.3.4 includes `platform install|status|update` plus a checksummed Core/dashboard payload, with separate Cloudflare account authorization; see [platform lifecycle](../../docs/platform-lifecycle.md). Disposable install/recovery and a genuine 0.3.3 → 0.3.4 upgrade passed in the owner’s Cloudflare account. This remains a trusted-team preview; independent-account acceptance with a full-scope platform CLI token is not yet complete. Check the registry for exact-version availability rather than assuming a moving tag contains the platform commands. No npm publication or cloud provisioning occurs during local verification.
66
+ Browser approval is protected by online Access/owner authorization and same-origin CSRF. Project-scoped credentials stay outside source/frontend env. Dev publishes to an already provisioned platform. Source 0.3.5 includes `platform install|status|update|adopt` plus a checksummed Core/dashboard payload, with separate Cloudflare account authorization; see [platform lifecycle](../../docs/platform-lifecycle.md). Disposable install/recovery and genuine 0.3.3 → 0.3.4 / 0.3.4 → 0.3.5 upgrades passed in the owner’s Cloudflare account. The existing personal panel also passed explicit external compiled-CLI adoption, preserving data/auth/files and namespace identities. This remains a trusted-team preview; independent-account acceptance with a full-scope platform CLI token is not yet complete. Check the registry for exact-version availability rather than assuming a moving tag contains the platform commands. No npm publication or cloud provisioning occurs during local verification.
67
67
 
68
68
  ## Immutable releases
69
69
 
@@ -289,13 +289,25 @@ function validateState(state, account, name) {
289
289
  if (!state || state.format !== 1 || state.account !== account || state.name !== name || !uuid.test(state.id ?? "") || !validName(name) || typeof state.owner !== "string" || !/^\S+@\S+\.\S+$/.test(state.owner) || state.owner.length > 254)
290
290
  throw Error("Unsupported installation state or account/name mismatch");
291
291
  const prefix = `flare-${name}-${state.id.replaceAll("-", "").slice(0, 12)}`;
292
+ const operator = state.origin === "operator";
293
+ if (state.origin !== undefined && !operator)
294
+ throw Error("Unsupported installation origin");
295
+ if (operator && (name !== "default" || !state.addresses?.custom || !/^\d{1,10}$/.test(state.rateLimitNamespace ?? "")))
296
+ throw Error("Unsupported operator installation identity");
297
+ const legacy = {
298
+ catalog: "flare-catalog",
299
+ bundles: "flare-builds",
300
+ files: "flare-files",
301
+ jobs: "flare-jobs",
302
+ dlq: "flare-jobs-dlq"
303
+ };
292
304
  for (const key of ["catalog", "bundles", "files", "jobs", "dlq"])
293
- if (!resourceName.test(state.resources?.[key]?.name ?? "") || state.resources[key].name !== (key === "catalog" ? `flare-${name}-catalog` : `${prefix}-${key}`))
305
+ if (!resourceName.test(state.resources?.[key]?.name ?? "") || state.resources[key].name !== (operator ? legacy[key] : key === "catalog" ? `flare-${name}-catalog` : `${prefix}-${key}`))
294
306
  throw Error("Invalid installation resource identity");
295
307
  if (state.resources.catalog.id !== null && !uuid.test(state.resources.catalog.id))
296
308
  throw Error("Invalid catalogue identity");
297
309
  for (const key of ["admin", "runtime"])
298
- if (!resourceName.test(state.workers?.[key]?.name ?? "") || state.workers[key].name !== `${prefix}-${key}`)
310
+ if (!resourceName.test(state.workers?.[key]?.name ?? "") || state.workers[key].name !== (operator ? `flare-${key}` : `${prefix}-${key}`))
299
311
  throw Error("Invalid Worker identity");
300
312
  if (typeof state.addresses?.custom !== "boolean" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(state.addresses.subdomain ?? ""))
301
313
  throw Error("Invalid installation address configuration");
@@ -307,7 +319,7 @@ function validateState(state, account, name) {
307
319
  throw Error("Admin and Runtime hostnames must differ");
308
320
  if (state.access && (!/^[a-f0-9-]{36}$/.test(state.access.id ?? "") || !/^[a-f0-9]{64}$/.test(state.access.aud ?? "") || !/^https:\/\/[a-z0-9-]+\.cloudflareaccess\.com$/.test(state.access.issuer ?? "")))
309
321
  throw Error("Invalid installation Access identity");
310
- if (state.operation && (!uuid.test(state.operation.id ?? "") || !["install", "update"].includes(state.operation.kind) || !/^[a-f0-9]{64}$/.test(state.operation.targetDigest ?? "") || !Array.isArray(state.operation.completed) || state.operation.completed.some((step) => ![
322
+ if (state.operation && (!uuid.test(state.operation.id ?? "") || !["install", "update", "adopt"].includes(state.operation.kind) || !/^[a-f0-9]{64}$/.test(state.operation.targetDigest ?? "") || !Array.isArray(state.operation.completed) || state.operation.completed.some((step) => ![
311
323
  "resources",
312
324
  "access",
313
325
  "migrations",
@@ -427,12 +439,14 @@ class InstallationStore {
427
439
  throw Error("Managed installation state is missing; refusing to recreate resources");
428
440
  return { state: local, row: null };
429
441
  }
430
- const candidates = (await this.cf.list("d1/database")).filter((db) => db.name === `flare-${this.name}-catalog`);
442
+ const candidates = (await this.cf.list("d1/database")).filter((db) => db.name === `flare-${this.name}-catalog` || this.name === "default" && db.name === "flare-catalog");
431
443
  const found = [];
432
444
  for (const candidate of candidates) {
433
445
  const row = await this.row(candidate.uuid);
434
446
  if (row)
435
447
  found.push(row);
448
+ else if (candidate.name === "flare-catalog")
449
+ throw Error("Legacy operator installation found. Use explicit platform adopt; do not reinstall over existing data.");
436
450
  else if (candidate.name !== local?.resources.catalog.name)
437
451
  throw Error("Unmanaged catalogue found. Refusing implicit adoption; use a distinct installation name or an explicit migration.");
438
452
  }
@@ -442,8 +456,6 @@ class InstallationStore {
442
456
  this.revision = found[0].revision;
443
457
  return { state: found[0].state, row: found[0] };
444
458
  }
445
- if (!local && this.name === "default" && (await this.cf.list("d1/database")).some((db) => db.name === "flare-catalog"))
446
- throw Error("Legacy operator installation found. Automatic adoption is not supported yet; do not reinstall over existing data.");
447
459
  return { state: local, row: null };
448
460
  }
449
461
  async initialize(state, ddl) {
@@ -695,7 +707,7 @@ function workerConfiguration(state, release, role, bootstrap = false) {
695
707
  ratelimits: [
696
708
  {
697
709
  name: "RATE_LIMITER",
698
- namespace_id: String(parseInt(state.id.replaceAll("-", "").slice(0, 7), 16)),
710
+ namespace_id: state.rateLimitNamespace ?? String(parseInt(state.id.replaceAll("-", "").slice(0, 7), 16)),
699
711
  simple: { limit: 600, period: 60 }
700
712
  }
701
713
  ],
@@ -720,7 +732,8 @@ async function confirm(options, state, plan, ask, log) {
720
732
  }
721
733
  async function assertAccess(cf, state) {
722
734
  const app = await cf.api(`access/apps/${state.access.id}`);
723
- if (app.domain !== state.addresses.adminHost || app.aud !== state.access.aud)
735
+ const coversHost = app.domain === state.addresses.adminHost || app.destinations?.some((d) => d.type === "public" && d.uri === state.addresses.adminHost);
736
+ if (!coversHost || app.aud !== state.access.aud || state.access.snapshot && configurationDigest(app) !== state.access.snapshot)
724
737
  throw Error("Access application drift; refusing to change policies or audiences");
725
738
  }
726
739
  async function remoteWorker(cf, name) {
@@ -901,6 +914,177 @@ async function verify(cf, state, release, fetcher, wait) {
901
914
  }
902
915
  }
903
916
  }
917
+ async function discoverOperator(cf, store, name) {
918
+ if (name !== "default")
919
+ throw Error("Operator adoption supports only --name default");
920
+ const databases = await cf.list("d1/database");
921
+ const matches = databases.filter((db) => db.name === "flare-catalog");
922
+ if (matches.length !== 1 || databases.some((db) => db.name === "flare-default-catalog"))
923
+ throw Error("Expected one legacy flare-catalog and no competing default catalogue");
924
+ const row = await store.row(matches[0].uuid);
925
+ const state = row?.state ?? await store.local();
926
+ if (state && (state.origin !== "operator" || state.resources.catalog.id !== matches[0].uuid))
927
+ throw Error("Catalogue/cache is owned by a different installation");
928
+ if (row)
929
+ store.revision = row.revision;
930
+ return { state, row };
931
+ }
932
+ async function prepareOperator(cf, inventory, release, options, dependencies) {
933
+ const owner = options.owner?.toLowerCase();
934
+ if (!owner || !/^\S+@\S+\.\S+$/.test(owner))
935
+ throw Error("Explicit --owner is required for adoption");
936
+ const database = inventory.databases.find((db) => db.name === "flare-catalog");
937
+ const member = (await cf.sql(database.uuid, "SELECT role FROM members WHERE email=?", [
938
+ owner
939
+ ])).results[0];
940
+ if (member?.role !== "owner")
941
+ throw Error("Existing owner membership does not match; roles are never rewritten");
942
+ const state = {
943
+ format: 1,
944
+ origin: "operator",
945
+ id: randomUUID2(),
946
+ account: cf.account,
947
+ name: "default",
948
+ owner,
949
+ resources: {
950
+ catalog: { name: "flare-catalog", id: database.uuid },
951
+ bundles: { name: "flare-builds", id: "flare-builds" },
952
+ files: { name: "flare-files", id: "flare-files" },
953
+ jobs: { name: "flare-jobs", id: null },
954
+ dlq: { name: "flare-jobs-dlq", id: null }
955
+ },
956
+ workers: {
957
+ runtime: { name: "flare-runtime" },
958
+ admin: { name: "flare-admin" }
959
+ },
960
+ addresses: { custom: true, subdomain: inventory.subdomain },
961
+ access: null,
962
+ installed: null,
963
+ operation: null,
964
+ snapshots: {},
965
+ versions: {},
966
+ domainSnapshots: {},
967
+ createdAt: Date.now()
968
+ };
969
+ const remote = {};
970
+ for (const role of ["runtime", "admin"]) {
971
+ remote[role] = await cf.api(`workers/scripts/flare-${role}/settings`);
972
+ state.versions[role] = await cf.currentVersion(`flare-${role}`);
973
+ state.snapshots[role] = settingsDigest(remote[role]);
974
+ state.domainSnapshots[role] = await domainSnapshot(cf, `flare-${role}`);
975
+ const endpoint = await cf.api(`workers/scripts/flare-${role}/subdomain`);
976
+ if (endpoint.enabled || endpoint.previews_enabled)
977
+ throw Error("Adoption requires custom domains with alternate endpoints disabled");
978
+ if (remote[role].logpush || remote[role].tail_consumers?.length || remote[role].tags?.length || Object.keys(remote[role].placement ?? {}).length || remote[role].limits?.cpu_ms !== 1000 || Object.keys(remote[role].limits ?? {}).some((key) => key !== "cpu_ms") || remote[role].observability?.enabled !== true || remote[role].observability?.head_sampling_rate !== 1 || remote[role].observability?.traces?.enabled === true || remote[role].observability?.logs?.enabled === false || remote[role].observability?.logs?.invocation_logs === false || remote[role].observability?.logs?.persist === false || remote[role].observability?.redact_query_string === true || remote[role].compatibility_flags?.join() !== "nodejs_compat")
979
+ throw Error("Unsupported operator Worker configuration; adoption does not silently reconcile custom settings");
980
+ }
981
+ const binding = (role, name, type) => {
982
+ const matches = remote[role].bindings?.filter((b) => b.name === name && b.type === type);
983
+ if (matches?.length !== 1)
984
+ throw Error(`Missing or ambiguous ${role} binding ${name}`);
985
+ return matches[0];
986
+ };
987
+ const allowed = {
988
+ runtime: [
989
+ "ADMIN_API",
990
+ "BUNDLES",
991
+ "FILES",
992
+ "JOBS",
993
+ "LOADER",
994
+ "RATE_LIMITER",
995
+ "SUPERVISORS",
996
+ "DEV_SIGNING_KEY",
997
+ "RUNTIME_URL",
998
+ "DASHBOARD_URL",
999
+ "DEV_TOKEN_ISSUER"
1000
+ ],
1001
+ admin: [
1002
+ "RUNTIME",
1003
+ "CATALOG",
1004
+ "ASSETS",
1005
+ "RUNTIME_URL",
1006
+ "DASHBOARD_URL",
1007
+ "ACCESS_ISSUER",
1008
+ "ACCESS_AUD"
1009
+ ]
1010
+ };
1011
+ for (const role of ["runtime", "admin"])
1012
+ if (remote[role].bindings.length !== allowed[role].length || remote[role].bindings.some((b) => !allowed[role].includes(b.name)))
1013
+ throw Error("Unsupported extra bindings; adoption must not remove operator capabilities");
1014
+ const runtimeUrl = binding("runtime", "RUNTIME_URL", "plain_text").text;
1015
+ const dashboardUrl = binding("admin", "DASHBOARD_URL", "plain_text").text;
1016
+ for (const value of [runtimeUrl, dashboardUrl]) {
1017
+ const url = new URL(value);
1018
+ if (url.origin !== value || url.protocol !== "https:")
1019
+ throw Error("Operator URL must be a plain HTTPS origin");
1020
+ }
1021
+ state.addresses.runtimeHost = new URL(runtimeUrl).hostname;
1022
+ state.addresses.adminHost = new URL(dashboardUrl).hostname;
1023
+ if (binding("admin", "RUNTIME_URL", "plain_text").text !== runtimeUrl || binding("runtime", "DASHBOARD_URL", "plain_text").text !== dashboardUrl || binding("runtime", "DEV_TOKEN_ISSUER", "plain_text").text !== runtimeUrl)
1024
+ throw Error("Operator URLs/identity issuer do not match; refusing identity migration");
1025
+ for (const role of ["runtime", "admin"]) {
1026
+ const own = inventory.domains.filter((d) => d.service === `flare-${role}`);
1027
+ if (own.length !== 1 || own[0].hostname !== state.addresses[role === "runtime" ? "runtimeHost" : "adminHost"] || !own[0].enabled || own[0].previews_enabled)
1028
+ throw Error("Operator custom domain does not match binding URL");
1029
+ }
1030
+ if (binding("admin", "CATALOG", "d1").id !== database.uuid && binding("admin", "CATALOG", "d1").database_id !== database.uuid)
1031
+ throw Error("Admin catalogue binding mismatch");
1032
+ for (const [role, key, target, entrypoint] of [
1033
+ ["admin", "RUNTIME", "flare-runtime", "RuntimeControl"],
1034
+ ["runtime", "ADMIN_API", "flare-admin", "AdminControl"]
1035
+ ]) {
1036
+ const b = binding(role, key, "service");
1037
+ if (b.service !== target || b.entrypoint !== entrypoint || b.environment && b.environment !== "production")
1038
+ throw Error("Operator service binding mismatch");
1039
+ }
1040
+ for (const [key, bucket] of [
1041
+ ["BUNDLES", "flare-builds"],
1042
+ ["FILES", "flare-files"]
1043
+ ])
1044
+ if (binding("runtime", key, "r2_bucket").bucket_name !== bucket)
1045
+ throw Error("Operator R2 binding mismatch");
1046
+ if (binding("runtime", "JOBS", "queue").queue_name !== "flare-jobs")
1047
+ throw Error("Operator Queue binding mismatch");
1048
+ binding("runtime", "LOADER", "worker_loader");
1049
+ binding("runtime", "DEV_SIGNING_KEY", "secret_text");
1050
+ binding("admin", "ASSETS", "assets");
1051
+ const supervisor = binding("runtime", "SUPERVISORS", "durable_object_namespace");
1052
+ if (supervisor.class_name !== "EnvironmentSupervisor" || supervisor.script_name)
1053
+ throw Error("Operator supervisor must be owned by Runtime");
1054
+ state.supervisorNamespace = supervisor.namespace_id;
1055
+ const rate = binding("runtime", "RATE_LIMITER", "ratelimit");
1056
+ if (rate.simple?.limit !== 600 || rate.simple?.period !== 60)
1057
+ throw Error("Unsupported operator rate-limit configuration");
1058
+ state.rateLimitNamespace = String(rate.namespace_id);
1059
+ const aud = binding("admin", "ACCESS_AUD", "plain_text").text;
1060
+ const app = inventory.apps.find((a) => a.aud === aud);
1061
+ if (!app || binding("admin", "ACCESS_ISSUER", "plain_text").text !== inventory.issuer)
1062
+ throw Error("Access application/issuer mismatch");
1063
+ const access = await cf.api(`access/apps/${app.id}`);
1064
+ state.access = {
1065
+ id: app.id,
1066
+ aud,
1067
+ issuer: inventory.issuer,
1068
+ snapshot: configurationDigest(access)
1069
+ };
1070
+ for (const key of ["jobs", "dlq"]) {
1071
+ const q = inventory.queues.find((q) => q.queue_name === state.resources[key].name);
1072
+ if (!q)
1073
+ throw Error("Operator queue is missing");
1074
+ state.resources[key].id = q.queue_id;
1075
+ }
1076
+ validateState(state, cf.account, "default");
1077
+ await assertResources(cf, state, inventory);
1078
+ const migrations = (await cf.sql(database.uuid, "SELECT name FROM d1_migrations")).results;
1079
+ if (migrations.some((m) => !migrationInventory(release.manifest).some((f) => f.name === m.name)))
1080
+ throw Error("Operator has unknown migrations; an explicit compatibility review is required");
1081
+ const response = await (dependencies.fetch ?? globalThis.fetch)(runtimeUrl + "/health", { redirect: "error", signal: AbortSignal.timeout(15000) });
1082
+ const health = await response.json();
1083
+ if (!response.ok || health.protocol !== release.manifest.protocol || health.runtimeAbi !== release.manifest.runtimeAbi || ![release.manifest.version, ...release.manifest.upgradeFrom].includes(health.version))
1084
+ throw Error("Operator runtime version/ABI is not an admitted adoption source");
1085
+ state.adoptedFrom = { version: health.version, at: Date.now() };
1086
+ return state;
1087
+ }
904
1088
  async function platform(options, dependencies = {}) {
905
1089
  const source = dependencies.release ?? await loadPlatformRelease();
906
1090
  const scratch = await mkdtemp(join3(tmpdir(), "flare-platform-payload-"));
@@ -918,8 +1102,8 @@ async function platform(options, dependencies = {}) {
918
1102
  }
919
1103
  async function runPlatform(options, dependencies = {}) {
920
1104
  const operation = options.operation;
921
- if (!["install", "status", "update"].includes(operation))
922
- throw Error("Supported: flare platform install|status|update; no deployment has occurred.");
1105
+ if (!["install", "status", "update", "adopt"].includes(operation))
1106
+ throw Error("Supported: flare platform install|status|update|adopt; no deployment has occurred.");
923
1107
  const name = options.name ?? "default";
924
1108
  if (!validName(name))
925
1109
  throw Error("Installation --name must be 1\u201320 lowercase letters, digits or hyphens, starting with a letter");
@@ -952,7 +1136,8 @@ async function runPlatform(options, dependencies = {}) {
952
1136
  if (cf.account !== account)
953
1137
  throw Error("Cloudflare account mismatch");
954
1138
  const store = new InstallationStore(cf, name, dependencies.storeOptions);
955
- const discovered = await store.discover();
1139
+ const adoption = operation === "adopt";
1140
+ const discovered = adoption ? await discoverOperator(cf, store, name) : await store.discover();
956
1141
  let state = discovered.state;
957
1142
  if (operation === "status")
958
1143
  return state ? publicState(state, release.manifest, discovered.row) : {
@@ -962,6 +1147,8 @@ async function runPlatform(options, dependencies = {}) {
962
1147
  target: release.manifest.version
963
1148
  };
964
1149
  const inventory = await preflight(cf);
1150
+ if (adoption && !state)
1151
+ state = await prepareOperator(cf, inventory, release, options, dependencies);
965
1152
  if (state) {
966
1153
  if (options.owner && options.owner.toLowerCase() !== state.owner || options["dashboard-host"] && options["dashboard-host"] !== state.addresses.adminHost || options["runtime-host"] && options["runtime-host"] !== state.addresses.runtimeHost || options.subdomain && options.subdomain !== state.addresses.subdomain)
967
1154
  throw Error("Existing installation identity cannot be changed by install/update");
@@ -987,21 +1174,21 @@ async function runPlatform(options, dependencies = {}) {
987
1174
  await assertNoDrift(cf, state);
988
1175
  if (state.installed.digest === release.manifest.digest)
989
1176
  return { ...publicState(state, release.manifest), unchanged: true };
990
- if (operation === "install")
1177
+ if (operation === "install" || adoption)
991
1178
  throw Error("Already installed. Use flare platform update to change the platform version");
992
1179
  }
993
- } else if (operation !== "install")
1180
+ } else if (operation !== "install" && !adoption)
994
1181
  throw Error("Installation is incomplete; resume platform install with the original release");
995
1182
  const plan = {
996
1183
  account,
997
1184
  installation: name,
998
1185
  operation,
999
- from: state.installed?.version ?? null,
1186
+ from: state.installed?.version ?? state.adoptedFrom?.version ?? null,
1000
1187
  to: release.manifest.version,
1001
1188
  digest: release.manifest.digest,
1002
1189
  dashboard: "https://" + state.addresses.adminHost,
1003
1190
  runtime: "https://" + state.addresses.runtimeHost,
1004
- steps: state.installed ? ["migrations", "runtime", "admin", "verify"] : steps,
1191
+ steps: state.installed || adoption ? ["migrations", "runtime", "admin", "verify"] : steps,
1005
1192
  preserve: [
1006
1193
  "Workers and DO namespace identities",
1007
1194
  "projects/dev/prod",
@@ -1040,9 +1227,14 @@ async function runPlatform(options, dependencies = {}) {
1040
1227
  await store.cache(state);
1041
1228
  }
1042
1229
  if (!discovered.row) {
1043
- const tables = (await cf.sql(state.resources.catalog.id, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_cf_%' AND name NOT IN ('flare_installation','d1_migrations')")).results;
1044
- if (tables.length)
1045
- throw Error("Unmanaged catalogue contains tables; refusing to claim existing data");
1230
+ if (!adoption) {
1231
+ const tables = (await cf.sql(state.resources.catalog.id, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_cf_%' AND name NOT IN ('flare_installation','d1_migrations')")).results;
1232
+ if (tables.length)
1233
+ throw Error("Unmanaged catalogue contains tables; refusing to claim existing data");
1234
+ } else {
1235
+ await assertResources(cf, state, inventory);
1236
+ await assertNoDrift(cf, state);
1237
+ }
1046
1238
  await store.initialize(state, await readFile3(join3(release.directory, "migrations/0006_platform_installation.sql"), "utf8"));
1047
1239
  }
1048
1240
  await store.acquire(state, options.resume === true);
@@ -1103,7 +1295,7 @@ async function runPlatform(options, dependencies = {}) {
1103
1295
  await supervisorNamespace(cf, state);
1104
1296
  };
1105
1297
  try {
1106
- if (!state.installed) {
1298
+ if (!state.installed && !adoption) {
1107
1299
  await checkpoint("resources", () => ensureResources(cf, state, store, inventory));
1108
1300
  await checkpoint("access", () => ensureAccess(cf, state, store, inventory));
1109
1301
  }
@@ -1115,7 +1307,7 @@ async function runPlatform(options, dependencies = {}) {
1115
1307
  if (!await migrationsApplied(cf, state, release.manifest))
1116
1308
  throw Error("D1 migrations did not finish");
1117
1309
  });
1118
- if (!state.installed) {
1310
+ if (!state.installed && !adoption) {
1119
1311
  await checkpoint("owner", async () => {
1120
1312
  await cf.sql(state.resources.catalog.id, "INSERT OR IGNORE INTO members(email,role) VALUES(?,?)", [state.owner, "owner"]);
1121
1313
  const row = (await cf.sql(state.resources.catalog.id, "SELECT role FROM members WHERE email=?", [state.owner])).results[0];
package/dist/cli/flare.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // packages/flare/package.json
4
4
  var package_default = {
5
5
  name: "@auersperg/flare",
6
- version: "0.3.4",
6
+ version: "0.3.5",
7
7
  license: "MIT",
8
8
  type: "module",
9
9
  description: "Flare client, server declarations and development CLI",
@@ -83,6 +83,7 @@ flare deploy Confirmed production build and deploy
83
83
 
84
84
  Platform lifecycle (Cloudflare account authorization):
85
85
  flare platform install
86
+ flare platform adopt --owner <existing owner email>
86
87
  flare platform status
87
88
  flare platform update --dry-run
88
89
  flare platform update
@@ -103,15 +104,16 @@ else if (command === "platform") {
103
104
  try {
104
105
  const operation = args.shift();
105
106
  if (["help", "--help"].includes(operation)) {
106
- console.log(`flare platform install|status|update [--account <ID>] [--name <name>]
107
+ console.log(`flare platform install|status|update|adopt [--account <ID>] [--name <name>]
107
108
  install: [--owner <email>] [--dashboard-host <host> --runtime-host <host>] [--subdomain <workers.dev prefix>]
108
- install/update: [--dry-run] [--confirm <account ID>] [--resume]
109
+ adopt: --owner <email> (existing standard flare-admin/flare-runtime/flare-catalog; default installation only)
110
+ install/update/adopt: [--dry-run] [--confirm <account ID>] [--resume]
109
111
  Uses the platform payload bundled with this CLI release. Never updates applications or runs a local dashboard. Requires separate Cloudflare account permissions; FLARE_DEPLOY_TOKEN is not an account token.`);
110
112
  } else {
111
- if (!["install", "status", "update"].includes(operation))
112
- throw Error("Supported: flare platform install|status|update; no deployment has occurred.");
113
+ if (!["install", "status", "update", "adopt"].includes(operation))
114
+ throw Error("Supported: flare platform install|status|update|adopt; no deployment has occurred.");
113
115
  const options = { operation };
114
- const allowed = operation === "install" ? [
116
+ const allowed = operation === "adopt" ? ["account", "name", "owner", "confirm", "dry-run", "resume"] : operation === "install" ? [
115
117
  "account",
116
118
  "name",
117
119
  "owner",
@@ -134,7 +136,7 @@ Uses the platform payload bundled with this CLI release. Never updates applicati
134
136
  options[key] = args[++i];
135
137
  }
136
138
  }
137
- const { platform } = await import("./chunk-5tjevcsj.js");
139
+ const { platform } = await import("./chunk-r3ceakrn.js");
138
140
  console.log(JSON.stringify(await platform(options), null, 2));
139
141
  }
140
142
  } catch (error) {
@@ -23,7 +23,7 @@ async function collectCatalog(database, now = Date.now()) {
23
23
  // packages/core/package.json
24
24
  var package_default = {
25
25
  name: "@flare/platform",
26
- version: "0.3.4",
26
+ version: "0.3.5",
27
27
  type: "module",
28
28
  license: "MIT",
29
29
  private: true,
@@ -0,0 +1 @@
1
+ import{a as e,r as t}from"./client.BeKNsjPN.js";import{S as n,_ as r,a as i,b as a,f as o,g as s,gt as c,ht as l,n as u,p as d,s as f,u as p,v as m,x as h,y as g}from"./common.DPoFQu44.js";import{s as _}from"./index.astro_astro_type_script_index_0_lang.BEuO3H0E.js";var v=e(t(),1),y=c();function b(e){let t=s(e.project,e.env),[c,b]=(0,v.useState)(``),[x,S]=(0,v.useState)(),C=p(t+`/auth-users`+(c?`?cursor=`+encodeURIComponent(c):``)),w=p(t+`/auth-settings`),T=e.session.role===`owner`,E=(n,r,i,a=[])=>{S({title:r,description:`Passwords are never displayed. Changes revoke existing sessions.`,productionId:e.env===`prod`?e.project.id:void 0,fields:a,submit:async r=>{try{await d(t+`/`+n,{...i,...o(a,e=>new FormData(r).get(e)),confirm:e.project.id})}catch(e){throw e instanceof Error&&e.message===`INVALID_PASSWORD`?Error(`Password must contain 6–128 characters. Spaces are allowed; the password is not trimmed.`):e instanceof Error&&e.message===`REGISTRATION_UNAVAILABLE`?Error(`This login is already registered. Choose another login or manage the existing user.`):e}C.refresh(),w.refresh()}})},D={name:`password`,type:`password`,required:!0,description:`6–128 characters.`};return(0,y.jsxs)(`div`,{className:`space-y-4 p-4`,children:[(0,y.jsx)(`h1`,{className:`page-title`,children:`Auth`}),(0,y.jsx)(f,{loading:C.loading||w.loading,error:C.error||w.error}),w.data&&(0,y.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,y.jsx)(_,{status:w.data.enabled?`Enabled`:`Disabled`}),(0,y.jsx)(_,{status:w.data.registration?`Public registration`:`Registration closed`}),T&&(0,y.jsx)(l,{variant:`outline`,onClick:()=>E(`auth-configure`,`Configure authentication`,{},[{name:`enabled`,type:`boolean`,value:w.data.enabled},{name:`registration`,type:`boolean`,value:w.data.registration}]),children:`Configure`}),T&&(0,y.jsx)(l,{onClick:()=>E(`auth-create`,`Create user`,{},[{name:`login`,type:`string`,required:!0},D]),children:`Create user`})]}),C.data&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(r,{children:[(0,y.jsx)(h,{children:(0,y.jsxs)(n,{children:[(0,y.jsx)(a,{children:`Login`}),(0,y.jsx)(a,{children:`User ID`}),(0,y.jsx)(a,{children:`Status`}),(0,y.jsx)(a,{children:`Created`}),(0,y.jsx)(a,{children:`Actions`})]})}),(0,y.jsx)(m,{children:C.data.users.map(e=>(0,y.jsxs)(n,{children:[(0,y.jsx)(g,{children:e.login}),(0,y.jsx)(g,{children:(0,y.jsx)(`code`,{children:e.id})}),(0,y.jsx)(g,{children:(0,y.jsx)(_,{status:e.disabled?`Blocked`:`Active`})}),(0,y.jsx)(g,{children:new Date(e.createdAt).toLocaleString()}),(0,y.jsx)(g,{children:(0,y.jsx)(`div`,{className:`flex flex-wrap gap-2`,children:T&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(l,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-password`,`Change user password`,{id:e.id},[D]),children:`Change password`}),(0,y.jsx)(l,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-revoke`,`Revoke all sessions`,{id:e.id}),children:`Revoke sessions`}),(0,y.jsx)(l,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-disable`,e.disabled?`Unblock user`:`Block user`,{id:e.id,disabled:!e.disabled}),children:e.disabled?`Unblock`:`Block`})]})})})]},e.id))})]}),!C.data.users.length&&(0,y.jsx)(u,{title:`No users`}),(0,y.jsxs)(`div`,{className:`flex gap-2`,children:[(0,y.jsx)(l,{variant:`outline`,disabled:!c,onClick:()=>b(``),children:`First page`}),(0,y.jsx)(l,{variant:`outline`,disabled:!C.data.next,onClick:()=>b(C.data.next),children:`Next page`})]})]}),x&&(0,y.jsx)(i,{modal:x,onClose:()=>S(void 0)})]})}export{b as default};
@@ -0,0 +1 @@
1
+ import{a as e,r as t}from"./client.BeKNsjPN.js";import{M as n,Y as r,gt as i,ht as a,j as o,k as s,m as c,p as l,r as u,t as d}from"./common.DPoFQu44.js";var f=e(t(),1),p=i();function m(){let e=new URLSearchParams(location.search).get(`cli`)??``,[t,i]=(0,f.useState)(``),[m,h]=(0,f.useState)([]),[g,_]=(0,f.useState)(`new`),[v,y]=(0,f.useState)(``),[b,x]=(0,f.useState)(``),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(!1);(0,f.useEffect)(()=>{let t=new AbortController;return Promise.all([l(`cli/approval?id=`+encodeURIComponent(e),void 0,t.signal),l(`projects`,void 0,t.signal)]).then(([e,t])=>{i(e.code),h(t),_(`new`)}).catch(e=>{t.signal.aborted||x(c(e))}),()=>t.abort()},[e]);async function E(){C(!0),x(``);try{let t=g;if(t===`new`){let e=await l(`projects`,{name:v,customer:`Applications`,origins:[]});t=e.id,h(t=>[...t,{id:e.id,name:v,customer:`Applications`}]),_(t)}await l(`cli/approval`,{id:e,project:t}),T(!0)}catch(e){x(c(e))}finally{C(!1)}}return(0,p.jsxs)(`main`,{className:`mx-auto max-w-lg p-8`,children:[(0,p.jsx)(`h1`,{className:`mb-4 text-xl font-semibold`,children:`Connect Flare CLI`}),w?(0,p.jsx)(`p`,{children:`Connected. Return to your terminal. You can close this tab.`}):(0,p.jsxs)(o,{children:[(0,p.jsx)(`p`,{children:`Approve only a login you just started. Compare this code with your terminal:`}),(0,p.jsx)(`strong`,{className:`text-2xl font-mono`,children:t||`Loading…`}),(0,p.jsx)(`p`,{children:`Grants this computer deployment access to the selected project, including production. The credential expires in 30 days and can be revoked in Settings.`}),(0,p.jsxs)(s,{children:[(0,p.jsx)(n,{children:`Project`}),(0,p.jsx)(d,{label:`CLI project`,value:g,onChange:_,items:[...m.map(e=>({value:e.id,label:e.name})),{value:`new`,label:`Create new project`}]})]}),g===`new`&&(0,p.jsxs)(s,{children:[(0,p.jsx)(n,{children:`New project name`}),(0,p.jsx)(r,{value:v,onChange:e=>y(e.target.value),maxLength:100})]}),b&&(0,p.jsx)(u,{children:b}),(0,p.jsx)(a,{disabled:S||!t||!g||g===`new`&&!v.trim(),onClick:E,children:`Approve connection`})]})]})}export{m as default};
@@ -0,0 +1 @@
1
+ import{a as e,r as t,t as n}from"./client.BeKNsjPN.js";var r=n(),i=e(t(),1),a=(0,i.memo)(({value:e,name:t,hydrate:n=!0})=>e==null||e.trim()===``?null:(0,i.createElement)(n?`astro-slot`:`astro-static-slot`,{name:t,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:e}}),()=>!0);function o(e){for(let t in e)if(t.startsWith(`__reactContainer`))return t}var s={class:`className`,for:`htmlFor`},c=0;function l(e,t,n){t===void 0&&(c+=1,t=c,n=0);let r={};for(let t of e.attributes){let e=s[t.name]||t.name;r[e]=t.value}if(r.key=`${t}-${n}`,e.firstChild===null)return(0,i.createElement)(e.localName,r);let a=0;return(0,i.createElement)(e.localName,r,Array.from(e.childNodes).map(e=>{if(e.nodeType===Node.TEXT_NODE)return e.data;if(e.nodeType===Node.ELEMENT_NODE)return a+=1,l(e,t,a)}).filter(e=>!!e))}function u(e,t){if(t&&e){let t=[],n=document.createElement(`template`);n.innerHTML=e;for(let e of n.content.children)t.push(l(e));return t}if(e)return(0,i.createElement)(a,{value:e})}var d=new WeakMap,f=(e,t)=>{let n=d.get(e);return n||(n=t(),d.set(e,n)),n},p=e=>(t,n,{default:s,...c},{client:l})=>{if(!e.hasAttribute(`ssr`))return;let d=e.getAttribute(`data-action-key`),p=e.getAttribute(`data-action-name`),m=e.getAttribute(`data-action-result`),h=d&&p&&m?[JSON.parse(m),d,p]:void 0,g={identifierPrefix:e.getAttribute(`prefix`),formState:h};for(let[e,t]of Object.entries(c))n[e]=(0,i.createElement)(a,{value:t,name:e});let _=(0,i.createElement)(t,n,u(s,e.hasAttribute(`data-react-children`))),v=o(e);if(v&&delete e[v],l===`only`)return(0,i.startTransition)(()=>{f(e,()=>{let t=(0,r.createRoot)(e);return e.addEventListener(`astro:unmount`,()=>t.unmount(),{once:!0}),t}).render(_)});(0,i.startTransition)(()=>{f(e,()=>{let t=(0,r.hydrateRoot)(e,_,g);return e.addEventListener(`astro:unmount`,()=>t.unmount(),{once:!0}),t}).render(_)})};export{p as default};