@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.
- package/README.md +1 -1
- package/dist/cli/{chunk-5tjevcsj.js → chunk-r3ceakrn.js} +212 -20
- package/dist/cli/flare.js +9 -7
- package/dist/platform/admin.js +1 -1
- package/dist/platform/dashboard/_astro/auth-view.UTGGCEHc.js +1 -0
- package/dist/platform/dashboard/_astro/cli-approval.BZAWmpiR.js +1 -0
- package/dist/platform/dashboard/_astro/client.Bcl1NrNO.js +1 -0
- package/dist/platform/dashboard/_astro/client.BeKNsjPN.js +9 -0
- package/dist/platform/dashboard/_astro/common.DPoFQu44.js +1 -0
- package/dist/platform/dashboard/_astro/data-view.BgYyTCMN.js +1 -0
- package/dist/platform/dashboard/_astro/functions-view.PUkng3T1.js +1 -0
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.BEuO3H0E.js +4 -0
- package/dist/platform/dashboard/_astro/index.lobU7uK5.css +2 -0
- package/dist/platform/dashboard/_astro/input-group.CJxPRHej.js +1 -0
- package/dist/platform/dashboard/_astro/sdk-view.COmbEZrF.js +1 -0
- package/dist/platform/dashboard/_astro/services-view.C34dy0dk.js +1 -0
- package/dist/platform/dashboard/_astro/settings-view.IuD2r5JW.js +3 -0
- package/dist/platform/dashboard/index.html +1 -1
- package/dist/platform/manifest.json +46 -81
- package/dist/platform/runtime.js +1 -1
- package/package.json +1 -1
- package/dist/platform/dashboard/_astro/auth-view.CuxQ12Fa.js +0 -1
- package/dist/platform/dashboard/_astro/badge.DwKWvVfO.js +0 -1
- package/dist/platform/dashboard/_astro/card.Bdqmram9.js +0 -1
- package/dist/platform/dashboard/_astro/cli-approval.VKPc8wYS.js +0 -1
- package/dist/platform/dashboard/_astro/client.DYdPL2z4.js +0 -9
- package/dist/platform/dashboard/_astro/common.HrRZXwD9.js +0 -1
- package/dist/platform/dashboard/_astro/dashboard.CH594XTw.js +0 -3
- package/dist/platform/dashboard/_astro/data-view.CcyUYvId.js +0 -1
- package/dist/platform/dashboard/_astro/function-badge.MlTRYkqM.js +0 -1
- package/dist/platform/dashboard/_astro/functions-view.C8JWdNAh.js +0 -1
- package/dist/platform/dashboard/_astro/index.DiiMDa7R.css +0 -2
- package/dist/platform/dashboard/_astro/input-group.3twGpQ77.js +0 -1
- package/dist/platform/dashboard/_astro/react-dom.DwX89IcG.js +0 -1
- package/dist/platform/dashboard/_astro/refresh-cw.B0Cj85jJ.js +0 -1
- package/dist/platform/dashboard/_astro/sdk-view.CRFkbEyS.js +0 -1
- package/dist/platform/dashboard/_astro/services-view.B2suYnaG.js +0 -1
- package/dist/platform/dashboard/_astro/settings-view.BFP47ySK.js +0 -3
- package/dist/platform/dashboard/_astro/status-badge.BV1dzbX9.js +0 -1
- package/dist/platform/dashboard/_astro/table-guide.DzBDP6fC.js +0 -2
- 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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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.
|
|
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
|
-
|
|
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-
|
|
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) {
|
package/dist/platform/admin.js
CHANGED
|
@@ -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};
|