@arcote.tech/arc-cli 0.7.32 → 0.8.1
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/.arc/platform/_input.css +61 -0
- package/.arc/platform/access.json +1 -0
- package/.arc/platform/browser/_entries/initial.ts +2 -0
- package/.arc/platform/server/_externals.json +1 -0
- package/.arc/platform/server/_server.js +9 -0
- package/.arc/platform/server/_server.js.map +9 -0
- package/dist/index.js +925 -365
- package/package.json +10 -10
- package/src/builder/access-extractor.ts +34 -7
- package/src/builder/framework-peers.ts +7 -1
- package/src/builder/module-builder.ts +49 -11
- package/src/builder/peer-shell.ts +146 -0
- package/src/commands/platform-deploy.ts +42 -1
- package/src/commands/platform-dev.ts +7 -2
- package/src/deploy/assets/terraform/main.tf +35 -1
- package/src/deploy/assets/terraform/variables.tf +6 -0
- package/src/deploy/assets.ts +41 -1
- package/src/deploy/bootstrap.ts +9 -1
- package/src/deploy/config.ts +61 -1
- package/src/deploy/survey.ts +15 -1
- package/src/deploy/terraform.ts +3 -0
- package/src/index.ts +10 -2
- package/src/platform/server.ts +448 -45
- package/src/platform/shared.ts +119 -8
- package/src/platform/startup.ts +208 -4
package/dist/index.js
CHANGED
|
@@ -10204,11 +10204,18 @@ function notifyTokenChange(scope) {
|
|
|
10204
10204
|
|
|
10205
10205
|
class AuthAdapter {
|
|
10206
10206
|
scopes = new Map;
|
|
10207
|
+
namespace;
|
|
10208
|
+
constructor(options) {
|
|
10209
|
+
this.namespace = options?.storageNamespace || undefined;
|
|
10210
|
+
}
|
|
10211
|
+
storageKey(scope) {
|
|
10212
|
+
return this.namespace ? `${TOKEN_PREFIX}${this.namespace}:${scope}` : TOKEN_PREFIX + scope;
|
|
10213
|
+
}
|
|
10207
10214
|
setToken(token, scope = "default") {
|
|
10208
10215
|
if (!token) {
|
|
10209
10216
|
this.scopes.delete(scope);
|
|
10210
10217
|
if (hasLocalStorage()) {
|
|
10211
|
-
localStorage.removeItem(
|
|
10218
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
10212
10219
|
notifyTokenChange(scope);
|
|
10213
10220
|
}
|
|
10214
10221
|
return;
|
|
@@ -10218,7 +10225,7 @@ class AuthAdapter {
|
|
|
10218
10225
|
if (parts.length !== 3) {
|
|
10219
10226
|
this.scopes.delete(scope);
|
|
10220
10227
|
if (hasLocalStorage())
|
|
10221
|
-
localStorage.removeItem(
|
|
10228
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
10222
10229
|
return;
|
|
10223
10230
|
}
|
|
10224
10231
|
const payload = JSON.parse(atob(parts[1]));
|
|
@@ -10232,13 +10239,13 @@ class AuthAdapter {
|
|
|
10232
10239
|
}
|
|
10233
10240
|
});
|
|
10234
10241
|
if (hasLocalStorage()) {
|
|
10235
|
-
localStorage.setItem(
|
|
10242
|
+
localStorage.setItem(this.storageKey(scope), token);
|
|
10236
10243
|
notifyTokenChange(scope);
|
|
10237
10244
|
}
|
|
10238
10245
|
} catch {
|
|
10239
10246
|
this.scopes.delete(scope);
|
|
10240
10247
|
if (hasLocalStorage())
|
|
10241
|
-
localStorage.removeItem(
|
|
10248
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
10242
10249
|
}
|
|
10243
10250
|
}
|
|
10244
10251
|
setDecoded(decoded, scope = "default") {
|
|
@@ -10247,10 +10254,13 @@ class AuthAdapter {
|
|
|
10247
10254
|
loadPersisted() {
|
|
10248
10255
|
if (!hasLocalStorage())
|
|
10249
10256
|
return;
|
|
10257
|
+
const prefix = this.namespace ? `${TOKEN_PREFIX}${this.namespace}:` : TOKEN_PREFIX;
|
|
10250
10258
|
for (let i = 0;i < localStorage.length; i++) {
|
|
10251
10259
|
const key = localStorage.key(i);
|
|
10252
|
-
if (key?.startsWith(
|
|
10253
|
-
const scope = key.slice(
|
|
10260
|
+
if (key?.startsWith(prefix)) {
|
|
10261
|
+
const scope = key.slice(prefix.length);
|
|
10262
|
+
if (!this.namespace && scope.includes(":"))
|
|
10263
|
+
continue;
|
|
10254
10264
|
const raw = localStorage.getItem(key);
|
|
10255
10265
|
if (raw) {
|
|
10256
10266
|
try {
|
|
@@ -10304,7 +10314,7 @@ class AuthAdapter {
|
|
|
10304
10314
|
clear() {
|
|
10305
10315
|
if (hasLocalStorage()) {
|
|
10306
10316
|
for (const scope of this.scopes.keys()) {
|
|
10307
|
-
localStorage.removeItem(
|
|
10317
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
10308
10318
|
}
|
|
10309
10319
|
}
|
|
10310
10320
|
this.scopes.clear();
|
|
@@ -11393,6 +11403,8 @@ class ArcFunction {
|
|
|
11393
11403
|
if (!tokenInstance) {
|
|
11394
11404
|
return false;
|
|
11395
11405
|
}
|
|
11406
|
+
if (!protection.check)
|
|
11407
|
+
continue;
|
|
11396
11408
|
const result = await protection.check(tokenInstance);
|
|
11397
11409
|
if (result === false) {
|
|
11398
11410
|
return false;
|
|
@@ -17839,11 +17851,18 @@ function notifyTokenChange2(scope) {
|
|
|
17839
17851
|
|
|
17840
17852
|
class AuthAdapter2 {
|
|
17841
17853
|
scopes = new Map;
|
|
17854
|
+
namespace;
|
|
17855
|
+
constructor(options) {
|
|
17856
|
+
this.namespace = options?.storageNamespace || undefined;
|
|
17857
|
+
}
|
|
17858
|
+
storageKey(scope) {
|
|
17859
|
+
return this.namespace ? `${TOKEN_PREFIX2}${this.namespace}:${scope}` : TOKEN_PREFIX2 + scope;
|
|
17860
|
+
}
|
|
17842
17861
|
setToken(token, scope = "default") {
|
|
17843
17862
|
if (!token) {
|
|
17844
17863
|
this.scopes.delete(scope);
|
|
17845
17864
|
if (hasLocalStorage2()) {
|
|
17846
|
-
localStorage.removeItem(
|
|
17865
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
17847
17866
|
notifyTokenChange2(scope);
|
|
17848
17867
|
}
|
|
17849
17868
|
return;
|
|
@@ -17853,7 +17872,7 @@ class AuthAdapter2 {
|
|
|
17853
17872
|
if (parts.length !== 3) {
|
|
17854
17873
|
this.scopes.delete(scope);
|
|
17855
17874
|
if (hasLocalStorage2())
|
|
17856
|
-
localStorage.removeItem(
|
|
17875
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
17857
17876
|
return;
|
|
17858
17877
|
}
|
|
17859
17878
|
const payload = JSON.parse(atob(parts[1]));
|
|
@@ -17867,13 +17886,13 @@ class AuthAdapter2 {
|
|
|
17867
17886
|
}
|
|
17868
17887
|
});
|
|
17869
17888
|
if (hasLocalStorage2()) {
|
|
17870
|
-
localStorage.setItem(
|
|
17889
|
+
localStorage.setItem(this.storageKey(scope), token);
|
|
17871
17890
|
notifyTokenChange2(scope);
|
|
17872
17891
|
}
|
|
17873
17892
|
} catch {
|
|
17874
17893
|
this.scopes.delete(scope);
|
|
17875
17894
|
if (hasLocalStorage2())
|
|
17876
|
-
localStorage.removeItem(
|
|
17895
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
17877
17896
|
}
|
|
17878
17897
|
}
|
|
17879
17898
|
setDecoded(decoded, scope = "default") {
|
|
@@ -17882,10 +17901,13 @@ class AuthAdapter2 {
|
|
|
17882
17901
|
loadPersisted() {
|
|
17883
17902
|
if (!hasLocalStorage2())
|
|
17884
17903
|
return;
|
|
17904
|
+
const prefix = this.namespace ? `${TOKEN_PREFIX2}${this.namespace}:` : TOKEN_PREFIX2;
|
|
17885
17905
|
for (let i = 0;i < localStorage.length; i++) {
|
|
17886
17906
|
const key = localStorage.key(i);
|
|
17887
|
-
if (key?.startsWith(
|
|
17888
|
-
const scope = key.slice(
|
|
17907
|
+
if (key?.startsWith(prefix)) {
|
|
17908
|
+
const scope = key.slice(prefix.length);
|
|
17909
|
+
if (!this.namespace && scope.includes(":"))
|
|
17910
|
+
continue;
|
|
17889
17911
|
const raw = localStorage.getItem(key);
|
|
17890
17912
|
if (raw) {
|
|
17891
17913
|
try {
|
|
@@ -17939,7 +17961,7 @@ class AuthAdapter2 {
|
|
|
17939
17961
|
clear() {
|
|
17940
17962
|
if (hasLocalStorage2()) {
|
|
17941
17963
|
for (const scope of this.scopes.keys()) {
|
|
17942
|
-
localStorage.removeItem(
|
|
17964
|
+
localStorage.removeItem(this.storageKey(scope));
|
|
17943
17965
|
}
|
|
17944
17966
|
}
|
|
17945
17967
|
this.scopes.clear();
|
|
@@ -19028,6 +19050,8 @@ class ArcFunction2 {
|
|
|
19028
19050
|
if (!tokenInstance) {
|
|
19029
19051
|
return false;
|
|
19030
19052
|
}
|
|
19053
|
+
if (!protection.check)
|
|
19054
|
+
continue;
|
|
19031
19055
|
const result = await protection.check(tokenInstance);
|
|
19032
19056
|
if (result === false) {
|
|
19033
19057
|
return false;
|
|
@@ -31298,20 +31322,20 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
|
|
|
31298
31322
|
}
|
|
31299
31323
|
|
|
31300
31324
|
// src/platform/shared.ts
|
|
31301
|
-
import { copyFileSync, existsSync as
|
|
31302
|
-
import { dirname as dirname8, join as
|
|
31325
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
31326
|
+
import { dirname as dirname8, join as join13 } from "path";
|
|
31303
31327
|
|
|
31304
31328
|
// src/builder/module-builder.ts
|
|
31305
31329
|
import { execSync } from "child_process";
|
|
31306
31330
|
import {
|
|
31307
|
-
existsSync as
|
|
31308
|
-
mkdirSync as
|
|
31309
|
-
readFileSync as
|
|
31331
|
+
existsSync as existsSync8,
|
|
31332
|
+
mkdirSync as mkdirSync7,
|
|
31333
|
+
readFileSync as readFileSync8,
|
|
31310
31334
|
readdirSync as readdirSync4,
|
|
31311
|
-
rmSync as
|
|
31312
|
-
writeFileSync as
|
|
31335
|
+
rmSync as rmSync3,
|
|
31336
|
+
writeFileSync as writeFileSync7
|
|
31313
31337
|
} from "fs";
|
|
31314
|
-
import { basename as
|
|
31338
|
+
import { basename as basename3, dirname as dirname6, join as join9, relative as relative3 } from "path";
|
|
31315
31339
|
import { builtinModules } from "module";
|
|
31316
31340
|
init_i18n();
|
|
31317
31341
|
init_compile();
|
|
@@ -31390,10 +31414,21 @@ var FRAMEWORK_PEERS = [...ARC_PEERS, ...REACT_PEERS];
|
|
|
31390
31414
|
var SHELL_EXTERNALS = [
|
|
31391
31415
|
...FRAMEWORK_PEERS,
|
|
31392
31416
|
"react/jsx-runtime",
|
|
31393
|
-
"react/
|
|
31417
|
+
"react-dom/client"
|
|
31394
31418
|
];
|
|
31395
31419
|
var FRAMEWORK_PEER_SET = new Set(FRAMEWORK_PEERS);
|
|
31396
31420
|
var SHELL_EXTERNAL_SET = new Set(SHELL_EXTERNALS);
|
|
31421
|
+
function shortNameOf(pkg) {
|
|
31422
|
+
if (pkg === "@arcote.tech/platform")
|
|
31423
|
+
return "platform";
|
|
31424
|
+
if (pkg.startsWith("@arcote.tech/"))
|
|
31425
|
+
return pkg.slice("@arcote.tech/".length);
|
|
31426
|
+
return pkg;
|
|
31427
|
+
}
|
|
31428
|
+
|
|
31429
|
+
// src/builder/peer-shell.ts
|
|
31430
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
31431
|
+
import { basename as basename2, join as join8 } from "path";
|
|
31397
31432
|
|
|
31398
31433
|
// src/builder/hash.ts
|
|
31399
31434
|
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync } from "fs";
|
|
@@ -31474,6 +31509,88 @@ function mtimeOf(path4) {
|
|
|
31474
31509
|
}
|
|
31475
31510
|
}
|
|
31476
31511
|
|
|
31512
|
+
// src/builder/peer-shell.ts
|
|
31513
|
+
var RUNTIME_GLOBAL = "__ARC_RT";
|
|
31514
|
+
function peerVersion(rootDir, pkg) {
|
|
31515
|
+
try {
|
|
31516
|
+
const p = JSON.parse(readFileSync7(join8(rootDir, "node_modules", pkg, "package.json"), "utf-8"));
|
|
31517
|
+
return p.version ?? "0.0.0";
|
|
31518
|
+
} catch {
|
|
31519
|
+
return "0.0.0";
|
|
31520
|
+
}
|
|
31521
|
+
}
|
|
31522
|
+
async function namedExportsOf(specifier) {
|
|
31523
|
+
try {
|
|
31524
|
+
const mod = await import(specifier);
|
|
31525
|
+
return Object.keys(mod).filter((k) => k !== "default" && /^[A-Za-z_$][\w$]*$/.test(k));
|
|
31526
|
+
} catch {
|
|
31527
|
+
return [];
|
|
31528
|
+
}
|
|
31529
|
+
}
|
|
31530
|
+
function runtimeExposePrelude() {
|
|
31531
|
+
const lines = SHELL_EXTERNALS.map((spec, i) => `import * as __rt${i} from ${JSON.stringify(spec)};`);
|
|
31532
|
+
const assigns = SHELL_EXTERNALS.map((spec, i) => ` ${JSON.stringify(spec)}: (__rt${i}.default ?? __rt${i}),
|
|
31533
|
+
${JSON.stringify(spec + "#ns")}: __rt${i},`);
|
|
31534
|
+
return lines.join(`
|
|
31535
|
+
`) + `
|
|
31536
|
+
globalThis.${RUNTIME_GLOBAL} = Object.assign(globalThis.${RUNTIME_GLOBAL} ?? {}, {
|
|
31537
|
+
` + assigns.join(`
|
|
31538
|
+
`) + `
|
|
31539
|
+
});
|
|
31540
|
+
`;
|
|
31541
|
+
}
|
|
31542
|
+
async function buildPeerShell(rootDir, outDir, cache, noCache) {
|
|
31543
|
+
mkdirSync6(outDir, { recursive: true });
|
|
31544
|
+
const peers = {};
|
|
31545
|
+
for (const pkg of FRAMEWORK_PEERS)
|
|
31546
|
+
peers[pkg] = peerVersion(rootDir, pkg);
|
|
31547
|
+
const namedBySpec = {};
|
|
31548
|
+
for (const spec of SHELL_EXTERNALS)
|
|
31549
|
+
namedBySpec[spec] = await namedExportsOf(spec);
|
|
31550
|
+
const unitId = "peer-shell";
|
|
31551
|
+
const inputHash = sha256OfJson({
|
|
31552
|
+
peers,
|
|
31553
|
+
externals: SHELL_EXTERNALS,
|
|
31554
|
+
named: namedBySpec,
|
|
31555
|
+
mode: "globalThis",
|
|
31556
|
+
rev: 5
|
|
31557
|
+
});
|
|
31558
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash)) {
|
|
31559
|
+
const cached = cache.units[unitId]?.outputHashes;
|
|
31560
|
+
if (cached?._manifest) {
|
|
31561
|
+
try {
|
|
31562
|
+
const m = JSON.parse(cached._manifest);
|
|
31563
|
+
const files = Object.values(m.imports).map((p) => basename2(p));
|
|
31564
|
+
if (files.every((f) => existsSync7(join8(outDir, f)))) {
|
|
31565
|
+
console.log(` \u2713 cached: ${unitId}`);
|
|
31566
|
+
return { ...m, cached: true };
|
|
31567
|
+
}
|
|
31568
|
+
} catch {}
|
|
31569
|
+
}
|
|
31570
|
+
}
|
|
31571
|
+
console.log(` building: ${unitId} (${SHELL_EXTERNALS.length} peers, globalThis)`);
|
|
31572
|
+
const imports = {};
|
|
31573
|
+
for (const specifier of SHELL_EXTERNALS) {
|
|
31574
|
+
const short = shortNameOf(specifier).replace(/\//g, "-");
|
|
31575
|
+
const named = namedBySpec[specifier] ?? [];
|
|
31576
|
+
const src = `const M = globalThis.${RUNTIME_GLOBAL}[${JSON.stringify(specifier)}];
|
|
31577
|
+
const N = globalThis.${RUNTIME_GLOBAL}[${JSON.stringify(specifier + "#ns")}] ?? M;
|
|
31578
|
+
export default M;
|
|
31579
|
+
` + named.map((n) => `export const ${n} = N[${JSON.stringify(n)}];`).join(`
|
|
31580
|
+
`) + `
|
|
31581
|
+
`;
|
|
31582
|
+
const hash = sha256Hex(src).slice(0, 16);
|
|
31583
|
+
const finalName = `${short}.${hash}.js`;
|
|
31584
|
+
writeFileSync6(join8(outDir, finalName), src);
|
|
31585
|
+
imports[specifier] = `/shell/${finalName}`;
|
|
31586
|
+
}
|
|
31587
|
+
const manifest = { imports, peers, cached: false };
|
|
31588
|
+
updateCache(cache, unitId, inputHash, {
|
|
31589
|
+
outputHashes: { _manifest: JSON.stringify(manifest) }
|
|
31590
|
+
});
|
|
31591
|
+
return manifest;
|
|
31592
|
+
}
|
|
31593
|
+
|
|
31477
31594
|
// src/builder/parallel.ts
|
|
31478
31595
|
import { cpus } from "os";
|
|
31479
31596
|
var DEFAULT_CONCURRENCY = Math.max(1, cpus().length - 1);
|
|
@@ -31527,10 +31644,10 @@ function isBuiltinSpec(spec) {
|
|
|
31527
31644
|
function versionFromImporter(importer, name) {
|
|
31528
31645
|
let dir = importer ? dirname6(importer) : "";
|
|
31529
31646
|
while (dir && dir !== dirname6(dir)) {
|
|
31530
|
-
const pj =
|
|
31531
|
-
if (
|
|
31647
|
+
const pj = join9(dir, "package.json");
|
|
31648
|
+
if (existsSync8(pj)) {
|
|
31532
31649
|
try {
|
|
31533
|
-
const json = JSON.parse(
|
|
31650
|
+
const json = JSON.parse(readFileSync8(pj, "utf-8"));
|
|
31534
31651
|
const spec = json.dependencies?.[name] ?? json.peerDependencies?.[name] ?? json.devDependencies?.[name];
|
|
31535
31652
|
if (typeof spec === "string")
|
|
31536
31653
|
return spec;
|
|
@@ -31584,11 +31701,7 @@ function jsxDevShimPlugin() {
|
|
|
31584
31701
|
namespace: "jsx-dev-shim"
|
|
31585
31702
|
}));
|
|
31586
31703
|
build2.onLoad({ filter: /.*/, namespace: "jsx-dev-shim" }, () => ({
|
|
31587
|
-
contents: `
|
|
31588
|
-
export const jsxDEV = jsx;
|
|
31589
|
-
export const jsxsDEV = jsxs;
|
|
31590
|
-
export { Fragment };
|
|
31591
|
-
`,
|
|
31704
|
+
contents: `export { jsx as jsxDEV, jsxs as jsxsDEV, Fragment } from "react/jsx-runtime";`,
|
|
31592
31705
|
loader: "ts"
|
|
31593
31706
|
}));
|
|
31594
31707
|
}
|
|
@@ -31601,13 +31714,13 @@ var SERVER_DEFINES = { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT:
|
|
|
31601
31714
|
var SERVER_ENTRY_FILE = "_server.js";
|
|
31602
31715
|
var SERVER_EXTERNALS_FILE = "_externals.json";
|
|
31603
31716
|
function discoverPackages(rootDir) {
|
|
31604
|
-
const rootPkg = JSON.parse(
|
|
31717
|
+
const rootPkg = JSON.parse(readFileSync8(join9(rootDir, "package.json"), "utf-8"));
|
|
31605
31718
|
const workspaceGlobs = rootPkg.workspaces ?? [];
|
|
31606
31719
|
const results = [];
|
|
31607
31720
|
for (const glob2 of workspaceGlobs) {
|
|
31608
31721
|
const base2 = glob2.replace("/*", "");
|
|
31609
|
-
const baseDir =
|
|
31610
|
-
if (!
|
|
31722
|
+
const baseDir = join9(rootDir, base2);
|
|
31723
|
+
if (!existsSync8(baseDir))
|
|
31611
31724
|
continue;
|
|
31612
31725
|
let entries;
|
|
31613
31726
|
try {
|
|
@@ -31616,25 +31729,25 @@ function discoverPackages(rootDir) {
|
|
|
31616
31729
|
continue;
|
|
31617
31730
|
}
|
|
31618
31731
|
for (const entry of entries) {
|
|
31619
|
-
const pkgPath =
|
|
31620
|
-
if (!
|
|
31732
|
+
const pkgPath = join9(baseDir, entry, "package.json");
|
|
31733
|
+
if (!existsSync8(pkgPath))
|
|
31621
31734
|
continue;
|
|
31622
|
-
const pkg = JSON.parse(
|
|
31735
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
31623
31736
|
if (pkg.name?.startsWith("@arcote.tech/"))
|
|
31624
31737
|
continue;
|
|
31625
|
-
const pkgDir =
|
|
31738
|
+
const pkgDir = join9(baseDir, entry);
|
|
31626
31739
|
const candidates = [
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31740
|
+
join9(pkgDir, "src", "index.ts"),
|
|
31741
|
+
join9(pkgDir, "src", "index.tsx"),
|
|
31742
|
+
join9(pkgDir, "index.ts"),
|
|
31743
|
+
join9(pkgDir, "index.tsx")
|
|
31631
31744
|
];
|
|
31632
|
-
const entrypoint = candidates.find((c) =>
|
|
31745
|
+
const entrypoint = candidates.find((c) => existsSync8(c)) ?? null;
|
|
31633
31746
|
if (!entrypoint)
|
|
31634
31747
|
continue;
|
|
31635
31748
|
results.push({
|
|
31636
31749
|
name: pkg.name,
|
|
31637
|
-
path:
|
|
31750
|
+
path: join9(baseDir, entry),
|
|
31638
31751
|
entrypoint,
|
|
31639
31752
|
packageJson: pkg
|
|
31640
31753
|
});
|
|
@@ -31663,7 +31776,7 @@ var sourceFilter = (rel) => {
|
|
|
31663
31776
|
return true;
|
|
31664
31777
|
};
|
|
31665
31778
|
function pkgSourceHash(pkg) {
|
|
31666
|
-
return sha256OfDir(
|
|
31779
|
+
return sha256OfDir(join9(pkg.path, "src"), sourceFilter);
|
|
31667
31780
|
}
|
|
31668
31781
|
function depVersionsHash(rootDir, pkg) {
|
|
31669
31782
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
@@ -31676,7 +31789,7 @@ function depVersionsHash(rootDir, pkg) {
|
|
|
31676
31789
|
}
|
|
31677
31790
|
async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
31678
31791
|
const unitId = `context-pkg:${pkg.name}:${client.name}`;
|
|
31679
|
-
const outDir =
|
|
31792
|
+
const outDir = join9(pkg.path, "dist", client.name);
|
|
31680
31793
|
const inputHash = sha256OfJson({
|
|
31681
31794
|
src: pkgSourceHash(pkg),
|
|
31682
31795
|
pkg: pkg.packageJson,
|
|
@@ -31685,7 +31798,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
31685
31798
|
target: client.target,
|
|
31686
31799
|
defines: client.defines
|
|
31687
31800
|
});
|
|
31688
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
31801
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join9(outDir, "main", "index.js")])) {
|
|
31689
31802
|
console.log(` \u2713 cached: ${pkg.name} (${client.name})`);
|
|
31690
31803
|
return { pkgName: pkg.name, client: client.name, declarationErrors: [], cached: true };
|
|
31691
31804
|
}
|
|
@@ -31695,7 +31808,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
31695
31808
|
const externals = [...peerDeps, ...Object.keys(allDeps)];
|
|
31696
31809
|
const result = await Bun.build({
|
|
31697
31810
|
entrypoints: [pkg.entrypoint],
|
|
31698
|
-
outdir:
|
|
31811
|
+
outdir: join9(outDir, "main"),
|
|
31699
31812
|
target: client.target,
|
|
31700
31813
|
format: "esm",
|
|
31701
31814
|
naming: "index.[ext]",
|
|
@@ -31765,7 +31878,7 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
|
|
|
31765
31878
|
}
|
|
31766
31879
|
async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
31767
31880
|
const contexts = packages.filter((p) => isContextPackage(p.packageJson));
|
|
31768
|
-
|
|
31881
|
+
mkdirSync7(serverDir, { recursive: true });
|
|
31769
31882
|
const srcByName = new Map(packages.map((p) => [p.name, p.entrypoint]));
|
|
31770
31883
|
const externalSet = new Set(FRAMEWORK_PEERS);
|
|
31771
31884
|
for (const p of packages) {
|
|
@@ -31786,8 +31899,8 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31786
31899
|
defines: SERVER_DEFINES,
|
|
31787
31900
|
sourcemap: "linked"
|
|
31788
31901
|
});
|
|
31789
|
-
const entryFileAbs =
|
|
31790
|
-
const externalsFileAbs =
|
|
31902
|
+
const entryFileAbs = join9(serverDir, SERVER_ENTRY_FILE);
|
|
31903
|
+
const externalsFileAbs = join9(serverDir, SERVER_EXTERNALS_FILE);
|
|
31791
31904
|
if (!noCache && isCacheHit(cache, unitId, inputHash, [entryFileAbs])) {
|
|
31792
31905
|
console.log(` \u2713 cached: ${unitId}`);
|
|
31793
31906
|
return {
|
|
@@ -31799,13 +31912,13 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31799
31912
|
console.log(` building: ${unitId} (${contexts.length} server modules)`);
|
|
31800
31913
|
for (const f of readdirSync4(serverDir)) {
|
|
31801
31914
|
if (f.endsWith(".js") || f.endsWith(".js.map")) {
|
|
31802
|
-
|
|
31915
|
+
rmSync3(join9(serverDir, f), { force: true });
|
|
31803
31916
|
}
|
|
31804
31917
|
}
|
|
31805
|
-
const tmpDir =
|
|
31806
|
-
|
|
31807
|
-
const entrySrc =
|
|
31808
|
-
|
|
31918
|
+
const tmpDir = join9(serverDir, "_entries");
|
|
31919
|
+
mkdirSync7(tmpDir, { recursive: true });
|
|
31920
|
+
const entrySrc = join9(tmpDir, SERVER_ENTRY_FILE.replace(/\.js$/, ".ts"));
|
|
31921
|
+
writeFileSync7(entrySrc, contexts.map((p) => `import "${p.name}";`).join(`
|
|
31809
31922
|
`) + `
|
|
31810
31923
|
`);
|
|
31811
31924
|
const recorded = new Map;
|
|
@@ -31828,7 +31941,7 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31828
31941
|
define: SERVER_DEFINES
|
|
31829
31942
|
});
|
|
31830
31943
|
} finally {
|
|
31831
|
-
|
|
31944
|
+
rmSync3(tmpDir, { recursive: true, force: true });
|
|
31832
31945
|
}
|
|
31833
31946
|
if (!result.success) {
|
|
31834
31947
|
for (const log2 of result.logs)
|
|
@@ -31839,31 +31952,33 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31839
31952
|
if (!entryOut) {
|
|
31840
31953
|
throw new Error("Server app build: entry not found in outputs");
|
|
31841
31954
|
}
|
|
31842
|
-
if (
|
|
31843
|
-
throw new Error(`Server app build: unexpected entry name ${
|
|
31955
|
+
if (basename3(entryOut.path) !== SERVER_ENTRY_FILE) {
|
|
31956
|
+
throw new Error(`Server app build: unexpected entry name ${basename3(entryOut.path)} (wanted ${SERVER_ENTRY_FILE})`);
|
|
31844
31957
|
}
|
|
31845
31958
|
const externals = [...recorded].filter(([name]) => !FRAMEWORK_PEER_SET.has(name)).map(([name, version]) => ({ name, version })).sort((a, b) => a.name.localeCompare(b.name));
|
|
31846
|
-
|
|
31959
|
+
writeFileSync7(externalsFileAbs, JSON.stringify(externals, null, 2) + `
|
|
31847
31960
|
`);
|
|
31848
31961
|
const outputHash = sha256OfDir(serverDir);
|
|
31849
31962
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
31850
31963
|
return { entryFile: SERVER_ENTRY_FILE, cached: false, externals };
|
|
31851
31964
|
}
|
|
31852
31965
|
function readServerExternals(path4) {
|
|
31853
|
-
if (!
|
|
31966
|
+
if (!existsSync8(path4))
|
|
31854
31967
|
return [];
|
|
31855
31968
|
try {
|
|
31856
|
-
const parsed = JSON.parse(
|
|
31969
|
+
const parsed = JSON.parse(readFileSync8(path4, "utf-8"));
|
|
31857
31970
|
return Array.isArray(parsed) ? parsed : [];
|
|
31858
31971
|
} catch {
|
|
31859
31972
|
return [];
|
|
31860
31973
|
}
|
|
31861
31974
|
}
|
|
31862
|
-
async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector) {
|
|
31863
|
-
|
|
31975
|
+
async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector, opts) {
|
|
31976
|
+
const federated = opts?.federated ?? false;
|
|
31977
|
+
const exposeRuntime = opts?.exposeRuntime ?? false;
|
|
31978
|
+
mkdirSync7(outDir, { recursive: true });
|
|
31864
31979
|
const publicMembers = plan.groups.get("public") ?? [];
|
|
31865
31980
|
const protectedGroups = plan.chunks.filter((c) => c !== "public").map((c) => ({ name: c, members: plan.groups.get(c) ?? [] })).filter((g) => g.members.length > 0);
|
|
31866
|
-
const unitId = "browser-app";
|
|
31981
|
+
const unitId = opts?.unitId ?? "browser-app";
|
|
31867
31982
|
const allMembers = [];
|
|
31868
31983
|
for (const m of publicMembers) {
|
|
31869
31984
|
allMembers.push({ name: m.pkg.name, group: "public", srcHash: pkgSourceHash(m.pkg) });
|
|
@@ -31879,7 +31994,11 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
|
|
|
31879
31994
|
"initial",
|
|
31880
31995
|
...protectedGroups.map((g) => g.name).sort()
|
|
31881
31996
|
],
|
|
31882
|
-
define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" }
|
|
31997
|
+
define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" },
|
|
31998
|
+
federated,
|
|
31999
|
+
exposeRuntime,
|
|
32000
|
+
externals: federated ? [...SHELL_EXTERNALS] : [],
|
|
32001
|
+
builderRev: 3
|
|
31883
32002
|
});
|
|
31884
32003
|
if (!noCache && isCacheHit(cache, unitId, inputHash)) {
|
|
31885
32004
|
const cached = cache.units[unitId]?.outputHashes;
|
|
@@ -31891,7 +32010,7 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
|
|
|
31891
32010
|
...Object.values(m.groups).map((g) => g.file),
|
|
31892
32011
|
...m.sharedChunks
|
|
31893
32012
|
];
|
|
31894
|
-
if (allFiles.every((f) =>
|
|
32013
|
+
if (allFiles.every((f) => existsSync8(join9(outDir, f)))) {
|
|
31895
32014
|
console.log(` \u2713 cached: ${unitId}`);
|
|
31896
32015
|
return { ...m, cached: true };
|
|
31897
32016
|
}
|
|
@@ -31899,25 +32018,26 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
|
|
|
31899
32018
|
}
|
|
31900
32019
|
}
|
|
31901
32020
|
console.log(` building: ${unitId} (initial: ${publicMembers.length} modules, groups: ${protectedGroups.map((g) => `${g.name}=${g.members.length}`).join(",") || "none"})`);
|
|
31902
|
-
if (
|
|
32021
|
+
if (existsSync8(outDir)) {
|
|
31903
32022
|
for (const f of readdirSync4(outDir)) {
|
|
31904
32023
|
if (f.endsWith(".js"))
|
|
31905
|
-
|
|
32024
|
+
rmSync3(join9(outDir, f), { force: true });
|
|
31906
32025
|
}
|
|
31907
32026
|
}
|
|
31908
|
-
const tmpDir =
|
|
31909
|
-
|
|
32027
|
+
const tmpDir = join9(outDir, "_entries");
|
|
32028
|
+
mkdirSync7(tmpDir, { recursive: true });
|
|
31910
32029
|
const importLines = (pkgs) => pkgs.map((m) => `import "${m.pkg.name}";`).join(`
|
|
31911
32030
|
`);
|
|
31912
|
-
const initialEntry =
|
|
31913
|
-
|
|
32031
|
+
const initialEntry = join9(tmpDir, "initial.ts");
|
|
32032
|
+
writeFileSync7(initialEntry, (exposeRuntime ? runtimeExposePrelude() + `
|
|
32033
|
+
` : "") + `${importLines(publicMembers)}
|
|
31914
32034
|
export { startApp } from "@arcote.tech/platform";
|
|
31915
32035
|
`);
|
|
31916
32036
|
const entryPaths = [initialEntry];
|
|
31917
32037
|
const groupModuleMap = new Map;
|
|
31918
32038
|
for (const g of protectedGroups) {
|
|
31919
|
-
const entry =
|
|
31920
|
-
|
|
32039
|
+
const entry = join9(tmpDir, `${g.name}.ts`);
|
|
32040
|
+
writeFileSync7(entry, `${importLines(g.members)}
|
|
31921
32041
|
`);
|
|
31922
32042
|
entryPaths.push(entry);
|
|
31923
32043
|
groupModuleMap.set(g.name, g.members.map((m) => m.moduleName));
|
|
@@ -31930,15 +32050,15 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31930
32050
|
allMemberPkgs.set(m.pkg.name, m.pkg);
|
|
31931
32051
|
const patchedPkgJsons = [];
|
|
31932
32052
|
for (const pkg of allMemberPkgs.values()) {
|
|
31933
|
-
const pkgJsonPath =
|
|
31934
|
-
if (!
|
|
32053
|
+
const pkgJsonPath = join9(pkg.path, "package.json");
|
|
32054
|
+
if (!existsSync8(pkgJsonPath))
|
|
31935
32055
|
continue;
|
|
31936
|
-
const original =
|
|
32056
|
+
const original = readFileSync8(pkgJsonPath, "utf-8");
|
|
31937
32057
|
const parsed = JSON.parse(original);
|
|
31938
32058
|
if (parsed.sideEffects === true)
|
|
31939
32059
|
continue;
|
|
31940
32060
|
parsed.sideEffects = true;
|
|
31941
|
-
|
|
32061
|
+
writeFileSync7(pkgJsonPath, JSON.stringify(parsed, null, 2) + `
|
|
31942
32062
|
`);
|
|
31943
32063
|
patchedPkgJsons.push({ path: pkgJsonPath, original });
|
|
31944
32064
|
}
|
|
@@ -31950,9 +32070,9 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31950
32070
|
splitting: true,
|
|
31951
32071
|
format: "esm",
|
|
31952
32072
|
target: "browser",
|
|
31953
|
-
external: [],
|
|
32073
|
+
external: federated ? [...SHELL_EXTERNALS] : [],
|
|
31954
32074
|
plugins: [
|
|
31955
|
-
singleReactPlugin(rootDir),
|
|
32075
|
+
...federated ? [] : [singleReactPlugin(rootDir)],
|
|
31956
32076
|
jsxDevShimPlugin(),
|
|
31957
32077
|
i18nExtractPlugin(i18nCollector, rootDir)
|
|
31958
32078
|
],
|
|
@@ -31966,9 +32086,9 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31966
32086
|
});
|
|
31967
32087
|
} finally {
|
|
31968
32088
|
for (const p of patchedPkgJsons)
|
|
31969
|
-
|
|
32089
|
+
writeFileSync7(p.path, p.original);
|
|
31970
32090
|
}
|
|
31971
|
-
|
|
32091
|
+
rmSync3(tmpDir, { recursive: true, force: true });
|
|
31972
32092
|
if (!result.success) {
|
|
31973
32093
|
for (const log2 of result.logs)
|
|
31974
32094
|
console.error(log2);
|
|
@@ -31979,16 +32099,16 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31979
32099
|
const groups = {};
|
|
31980
32100
|
const sharedChunks = [];
|
|
31981
32101
|
for (const out of result.outputs) {
|
|
31982
|
-
const name =
|
|
32102
|
+
const name = basename3(out.path);
|
|
31983
32103
|
if (out.kind === "entry-point") {
|
|
31984
|
-
const bytes =
|
|
32104
|
+
const bytes = readFileSync8(out.path);
|
|
31985
32105
|
const hash = sha256Hex(bytes).slice(0, 16);
|
|
31986
32106
|
const stem = name.replace(/\.js$/, "");
|
|
31987
32107
|
const finalName = `${stem}.${hash}.js`;
|
|
31988
|
-
const finalPath =
|
|
31989
|
-
|
|
31990
|
-
|
|
31991
|
-
|
|
32108
|
+
const finalPath = join9(outDir, finalName);
|
|
32109
|
+
rmSync3(finalPath, { force: true });
|
|
32110
|
+
writeFileSync7(finalPath, bytes);
|
|
32111
|
+
rmSync3(out.path, { force: true });
|
|
31992
32112
|
if (stem === "initial") {
|
|
31993
32113
|
initialFile = finalName;
|
|
31994
32114
|
initialHash = hash;
|
|
@@ -32018,21 +32138,21 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
32018
32138
|
return manifest;
|
|
32019
32139
|
}
|
|
32020
32140
|
async function buildTranslations(rootDir, arcDir, cache, noCache) {
|
|
32021
|
-
const localesDir =
|
|
32022
|
-
if (!
|
|
32141
|
+
const localesDir = join9(rootDir, "locales");
|
|
32142
|
+
if (!existsSync8(localesDir))
|
|
32023
32143
|
return;
|
|
32024
32144
|
const unitId = "translations";
|
|
32025
|
-
const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) =>
|
|
32145
|
+
const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) => join9(localesDir, f));
|
|
32026
32146
|
if (poFiles.length === 0)
|
|
32027
32147
|
return;
|
|
32028
32148
|
const inputHash = sha256OfFiles(poFiles);
|
|
32029
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
32149
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join9(arcDir, "locales")])) {
|
|
32030
32150
|
console.log(` \u2713 cached: translations`);
|
|
32031
32151
|
return;
|
|
32032
32152
|
}
|
|
32033
32153
|
console.log(` building: translations (${poFiles.length} catalog(s))`);
|
|
32034
|
-
compileAllCatalogs(localesDir,
|
|
32035
|
-
const jsonFiles = readdirSync4(
|
|
32154
|
+
compileAllCatalogs(localesDir, join9(arcDir, "locales"));
|
|
32155
|
+
const jsonFiles = readdirSync4(join9(arcDir, "locales")).filter((f) => f.endsWith(".json")).map((f) => join9(arcDir, "locales", f));
|
|
32036
32156
|
const outputHash = sha256OfFiles(jsonFiles);
|
|
32037
32157
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
32038
32158
|
}
|
|
@@ -32099,10 +32219,10 @@ var TAILWIND_INPUT_TEMPLATE = (rootRel) => `@import "tailwindcss";
|
|
|
32099
32219
|
}
|
|
32100
32220
|
`;
|
|
32101
32221
|
async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache) {
|
|
32102
|
-
|
|
32103
|
-
const inputCss =
|
|
32104
|
-
const outputCss =
|
|
32105
|
-
const themeOutput =
|
|
32222
|
+
mkdirSync7(arcDir, { recursive: true });
|
|
32223
|
+
const inputCss = join9(arcDir, "_input.css");
|
|
32224
|
+
const outputCss = join9(arcDir, "styles.css");
|
|
32225
|
+
const themeOutput = join9(arcDir, "theme.css");
|
|
32106
32226
|
const rootRel = relative3(arcDir, rootDir).replace(/\\/g, "/");
|
|
32107
32227
|
const inputCssContent = TAILWIND_INPUT_TEMPLATE(rootRel);
|
|
32108
32228
|
const tsxFilter = (rel) => {
|
|
@@ -32114,15 +32234,15 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
32114
32234
|
};
|
|
32115
32235
|
const wsHashes = {};
|
|
32116
32236
|
for (const p of packages) {
|
|
32117
|
-
wsHashes[p.name] = sha256OfDir(
|
|
32237
|
+
wsHashes[p.name] = sha256OfDir(join9(p.path, "src"), tsxFilter);
|
|
32118
32238
|
}
|
|
32119
|
-
const platformSrc =
|
|
32120
|
-
const arcDsSrc =
|
|
32239
|
+
const platformSrc = join9(rootDir, "node_modules", "@arcote.tech", "platform", "src");
|
|
32240
|
+
const arcDsSrc = join9(rootDir, "node_modules", "@arcote.tech", "arc-ds", "src");
|
|
32121
32241
|
const frameworkHashes = {
|
|
32122
32242
|
platform: sha256OfDir(platformSrc, tsxFilter),
|
|
32123
32243
|
arcDs: sha256OfDir(arcDsSrc, tsxFilter)
|
|
32124
32244
|
};
|
|
32125
|
-
const themeContent = themePath &&
|
|
32245
|
+
const themeContent = themePath && existsSync8(join9(rootDir, themePath)) ? readFileSync8(join9(rootDir, themePath)) : null;
|
|
32126
32246
|
const themeHash = themeContent ? sha256Hex(themeContent) : null;
|
|
32127
32247
|
const unitId = "styles";
|
|
32128
32248
|
const inputHash = sha256OfJson({
|
|
@@ -32139,16 +32259,16 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
32139
32259
|
return;
|
|
32140
32260
|
}
|
|
32141
32261
|
console.log(` building: styles`);
|
|
32142
|
-
|
|
32262
|
+
writeFileSync7(inputCss, inputCssContent);
|
|
32143
32263
|
execSync(`bunx @tailwindcss/cli -i ${inputCss} -o ${outputCss} --minify`, {
|
|
32144
32264
|
cwd: rootDir,
|
|
32145
32265
|
stdio: "inherit"
|
|
32146
32266
|
});
|
|
32147
32267
|
if (themePath && themeContent) {
|
|
32148
|
-
|
|
32268
|
+
writeFileSync7(themeOutput, themeContent);
|
|
32149
32269
|
}
|
|
32150
32270
|
const outFiles = [outputCss];
|
|
32151
|
-
if (themePath &&
|
|
32271
|
+
if (themePath && existsSync8(themeOutput))
|
|
32152
32272
|
outFiles.push(themeOutput);
|
|
32153
32273
|
const outputHash = sha256OfFiles(outFiles);
|
|
32154
32274
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
@@ -32157,20 +32277,20 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
32157
32277
|
// src/builder/access-extractor.ts
|
|
32158
32278
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
32159
32279
|
import {
|
|
32160
|
-
existsSync as
|
|
32161
|
-
mkdirSync as
|
|
32162
|
-
readFileSync as
|
|
32280
|
+
existsSync as existsSync9,
|
|
32281
|
+
mkdirSync as mkdirSync8,
|
|
32282
|
+
readFileSync as readFileSync9,
|
|
32163
32283
|
unlinkSync as unlinkSync2,
|
|
32164
|
-
writeFileSync as
|
|
32284
|
+
writeFileSync as writeFileSync8
|
|
32165
32285
|
} from "fs";
|
|
32166
|
-
import { join as
|
|
32286
|
+
import { join as join10 } from "path";
|
|
32167
32287
|
async function extractAccessMap(rootDir, serverBundlePath) {
|
|
32168
|
-
const serverBundles =
|
|
32169
|
-
const workerDir =
|
|
32170
|
-
|
|
32171
|
-
const workerPath =
|
|
32172
|
-
const outPath =
|
|
32173
|
-
|
|
32288
|
+
const serverBundles = existsSync9(serverBundlePath) ? [{ name: "server", path: serverBundlePath }] : [];
|
|
32289
|
+
const workerDir = join10(rootDir, ".arc", ".tmp");
|
|
32290
|
+
mkdirSync8(workerDir, { recursive: true });
|
|
32291
|
+
const workerPath = join10(workerDir, `access-extractor-${Date.now()}.mjs`);
|
|
32292
|
+
const outPath = join10(workerDir, `access-${Date.now()}.json`);
|
|
32293
|
+
writeFileSync8(workerPath, WORKER_SOURCE);
|
|
32174
32294
|
try {
|
|
32175
32295
|
const proc2 = spawn2({
|
|
32176
32296
|
cmd: ["bun", "run", workerPath],
|
|
@@ -32187,7 +32307,7 @@ async function extractAccessMap(rootDir, serverBundlePath) {
|
|
|
32187
32307
|
if (exit !== 0) {
|
|
32188
32308
|
throw new Error(`access-extractor subprocess exited with ${exit}`);
|
|
32189
32309
|
}
|
|
32190
|
-
return JSON.parse(
|
|
32310
|
+
return JSON.parse(readFileSync9(outPath, "utf-8"));
|
|
32191
32311
|
} finally {
|
|
32192
32312
|
try {
|
|
32193
32313
|
unlinkSync2(workerPath);
|
|
@@ -32224,23 +32344,35 @@ for (const { name, path } of bundles) {
|
|
|
32224
32344
|
}
|
|
32225
32345
|
}
|
|
32226
32346
|
|
|
32227
|
-
const
|
|
32228
|
-
for (const [name,
|
|
32229
|
-
|
|
32230
|
-
rules: (
|
|
32347
|
+
const access = {};
|
|
32348
|
+
for (const [name, a] of platform.getAllModuleAccess()) {
|
|
32349
|
+
access[name] = {
|
|
32350
|
+
rules: (a.rules ?? []).map((r) => ({
|
|
32231
32351
|
token: { name: r.token?.name ?? "" },
|
|
32232
32352
|
hasCheck: typeof r.check === "function",
|
|
32233
32353
|
})),
|
|
32234
32354
|
};
|
|
32235
32355
|
}
|
|
32236
32356
|
|
|
32357
|
+
const exposure = {};
|
|
32358
|
+
const getExposure = platform.getAllModuleExposure;
|
|
32359
|
+
if (typeof getExposure === "function") {
|
|
32360
|
+
for (const [name, e] of getExposure()) {
|
|
32361
|
+
exposure[name] = {
|
|
32362
|
+
exposure: e.exposure,
|
|
32363
|
+
permissions: e.permissions ?? [],
|
|
32364
|
+
slots: e.slots ?? [],
|
|
32365
|
+
};
|
|
32366
|
+
}
|
|
32367
|
+
}
|
|
32368
|
+
|
|
32237
32369
|
const { writeFileSync } = await import("node:fs");
|
|
32238
|
-
writeFileSync(out, JSON.stringify(
|
|
32370
|
+
writeFileSync(out, JSON.stringify({ access, exposure }, null, 2) + "\\n");
|
|
32239
32371
|
`.trim();
|
|
32240
32372
|
|
|
32241
32373
|
// src/builder/chunk-planner.ts
|
|
32242
|
-
import { readFileSync as
|
|
32243
|
-
import { basename as
|
|
32374
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
32375
|
+
import { basename as basename4, join as join11 } from "path";
|
|
32244
32376
|
var PUBLIC_CHUNK = "public";
|
|
32245
32377
|
function planChunks(packages, accessMap) {
|
|
32246
32378
|
const assignments = new Map;
|
|
@@ -32253,7 +32385,7 @@ function planChunks(packages, accessMap) {
|
|
|
32253
32385
|
pkg,
|
|
32254
32386
|
chunk,
|
|
32255
32387
|
moduleName,
|
|
32256
|
-
safeName:
|
|
32388
|
+
safeName: basename4(pkg.path)
|
|
32257
32389
|
};
|
|
32258
32390
|
assignments.set(moduleName, entry);
|
|
32259
32391
|
const bucket = groups.get(chunk);
|
|
@@ -32283,7 +32415,7 @@ function collectModuleNames(dir, acc) {
|
|
|
32283
32415
|
for (const e of entries) {
|
|
32284
32416
|
if (e === "node_modules" || e === "dist")
|
|
32285
32417
|
continue;
|
|
32286
|
-
const full =
|
|
32418
|
+
const full = join11(dir, e);
|
|
32287
32419
|
let isDir = false;
|
|
32288
32420
|
try {
|
|
32289
32421
|
isDir = statSync2(full).isDirectory();
|
|
@@ -32293,7 +32425,7 @@ function collectModuleNames(dir, acc) {
|
|
|
32293
32425
|
if (isDir) {
|
|
32294
32426
|
collectModuleNames(full, acc);
|
|
32295
32427
|
} else if (e.endsWith(".ts") || e.endsWith(".tsx")) {
|
|
32296
|
-
const src =
|
|
32428
|
+
const src = readFileSync10(full, "utf-8");
|
|
32297
32429
|
MODULE_CALL.lastIndex = 0;
|
|
32298
32430
|
let m;
|
|
32299
32431
|
while (m = MODULE_CALL.exec(src))
|
|
@@ -32305,7 +32437,7 @@ function assertOneModulePerPackage(packages) {
|
|
|
32305
32437
|
const offenders = [];
|
|
32306
32438
|
for (const pkg of packages) {
|
|
32307
32439
|
const names = new Set;
|
|
32308
|
-
collectModuleNames(
|
|
32440
|
+
collectModuleNames(join11(pkg.path, "src"), names);
|
|
32309
32441
|
if (names.size > 1) {
|
|
32310
32442
|
offenders.push({ pkg: pkg.name, modules: [...names].sort() });
|
|
32311
32443
|
}
|
|
@@ -32337,11 +32469,11 @@ function resolveChunk(moduleName, access) {
|
|
|
32337
32469
|
|
|
32338
32470
|
// src/builder/dependency-collector.ts
|
|
32339
32471
|
import { createHash } from "crypto";
|
|
32340
|
-
import { existsSync as
|
|
32341
|
-
import { basename as
|
|
32472
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
32473
|
+
import { basename as basename5, dirname as dirname7, join as join12 } from "path";
|
|
32342
32474
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
32343
32475
|
function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], serverExternals = []) {
|
|
32344
|
-
|
|
32476
|
+
mkdirSync9(arcDir, { recursive: true });
|
|
32345
32477
|
const versions = resolveFrameworkVersions(rootDir, packages);
|
|
32346
32478
|
mergeServerExternals(versions, rootDir, packages, serverExternals);
|
|
32347
32479
|
for (const { name, version } of sharedDeps) {
|
|
@@ -32350,7 +32482,7 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], server
|
|
|
32350
32482
|
try {
|
|
32351
32483
|
const cliDir = dirname7(fileURLToPath6(import.meta.url));
|
|
32352
32484
|
const arcOtelPkgPath = Bun.resolveSync("@arcote.tech/arc-otel/package.json", cliDir);
|
|
32353
|
-
const arcOtelPkg = JSON.parse(
|
|
32485
|
+
const arcOtelPkg = JSON.parse(readFileSync11(arcOtelPkgPath, "utf-8"));
|
|
32354
32486
|
for (const [name, spec] of Object.entries(arcOtelPkg.dependencies ?? {})) {
|
|
32355
32487
|
if (name.startsWith("@opentelemetry/")) {
|
|
32356
32488
|
versions[name] = spec;
|
|
@@ -32361,10 +32493,10 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], server
|
|
|
32361
32493
|
console.warn(`[arc-otel] could not resolve @arcote.tech/arc-otel \u2014 image will run without telemetry deps: ${e.message}`);
|
|
32362
32494
|
}
|
|
32363
32495
|
let rootArc;
|
|
32364
|
-
const rootPkgPath =
|
|
32365
|
-
if (
|
|
32496
|
+
const rootPkgPath = join12(rootDir, "package.json");
|
|
32497
|
+
if (existsSync10(rootPkgPath)) {
|
|
32366
32498
|
try {
|
|
32367
|
-
const rootPkg = JSON.parse(
|
|
32499
|
+
const rootPkg = JSON.parse(readFileSync11(rootPkgPath, "utf-8"));
|
|
32368
32500
|
if (rootPkg.arc && typeof rootPkg.arc === "object")
|
|
32369
32501
|
rootArc = rootPkg.arc;
|
|
32370
32502
|
} catch {}
|
|
@@ -32376,11 +32508,11 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], server
|
|
|
32376
32508
|
dependencies: versions,
|
|
32377
32509
|
...rootArc ? { arc: rootArc } : {}
|
|
32378
32510
|
};
|
|
32379
|
-
const manifestPath =
|
|
32380
|
-
|
|
32511
|
+
const manifestPath = join12(arcDir, "package.json");
|
|
32512
|
+
writeFileSync9(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
32381
32513
|
`);
|
|
32382
|
-
const hash = sha256Hex2(
|
|
32383
|
-
|
|
32514
|
+
const hash = sha256Hex2(readFileSync11(manifestPath));
|
|
32515
|
+
writeFileSync9(join12(arcDir, ".deps-hash"), hash + `
|
|
32384
32516
|
`);
|
|
32385
32517
|
return { hash, manifestPath };
|
|
32386
32518
|
}
|
|
@@ -32388,7 +32520,7 @@ function sha256Hex2(bytes) {
|
|
|
32388
32520
|
return createHash("sha256").update(bytes).digest("hex");
|
|
32389
32521
|
}
|
|
32390
32522
|
function resolveFrameworkVersions(rootDir, packages) {
|
|
32391
|
-
const rootPkg = JSON.parse(
|
|
32523
|
+
const rootPkg = JSON.parse(readFileSync11(join12(rootDir, "package.json"), "utf-8"));
|
|
32392
32524
|
const rootDeps = rootPkg.dependencies ?? {};
|
|
32393
32525
|
const rootDevDeps = rootPkg.devDependencies ?? {};
|
|
32394
32526
|
const required = new Set(FRAMEWORK_PEERS);
|
|
@@ -32419,7 +32551,7 @@ function findWorkspaceSpec(packages, name) {
|
|
|
32419
32551
|
return;
|
|
32420
32552
|
}
|
|
32421
32553
|
function mergeServerExternals(versions, rootDir, packages, externals) {
|
|
32422
|
-
const rootPkg = JSON.parse(
|
|
32554
|
+
const rootPkg = JSON.parse(readFileSync11(join12(rootDir, "package.json"), "utf-8"));
|
|
32423
32555
|
const rootDeps = rootPkg.dependencies ?? {};
|
|
32424
32556
|
const rootDevDeps = rootPkg.devDependencies ?? {};
|
|
32425
32557
|
for (const { name, version } of externals) {
|
|
@@ -32447,9 +32579,9 @@ function resolveWorkspace() {
|
|
|
32447
32579
|
process.exit(1);
|
|
32448
32580
|
}
|
|
32449
32581
|
const rootDir = dirname8(packageJsonPath);
|
|
32450
|
-
const rootPkg = JSON.parse(
|
|
32582
|
+
const rootPkg = JSON.parse(readFileSync12(packageJsonPath, "utf-8"));
|
|
32451
32583
|
const appName = rootPkg.name ?? "Arc App";
|
|
32452
|
-
const arcDir =
|
|
32584
|
+
const arcDir = join13(rootDir, ".arc", "platform");
|
|
32453
32585
|
log2("Scanning workspaces...");
|
|
32454
32586
|
const packages = discoverPackages(rootDir);
|
|
32455
32587
|
if (packages.length > 0) {
|
|
@@ -32459,10 +32591,10 @@ function resolveWorkspace() {
|
|
|
32459
32591
|
}
|
|
32460
32592
|
let manifest;
|
|
32461
32593
|
for (const name of ["manifest.json", "manifest.webmanifest"]) {
|
|
32462
|
-
const manifestPath =
|
|
32463
|
-
if (
|
|
32594
|
+
const manifestPath = join13(rootDir, name);
|
|
32595
|
+
if (existsSync11(manifestPath)) {
|
|
32464
32596
|
try {
|
|
32465
|
-
const data = JSON.parse(
|
|
32597
|
+
const data = JSON.parse(readFileSync12(manifestPath, "utf-8"));
|
|
32466
32598
|
const icons = data.icons;
|
|
32467
32599
|
manifest = {
|
|
32468
32600
|
path: manifestPath,
|
|
@@ -32481,9 +32613,9 @@ function resolveWorkspace() {
|
|
|
32481
32613
|
rootPkg,
|
|
32482
32614
|
appName,
|
|
32483
32615
|
arcDir,
|
|
32484
|
-
browserDir:
|
|
32485
|
-
assetsDir:
|
|
32486
|
-
publicDir:
|
|
32616
|
+
browserDir: join13(arcDir, "browser"),
|
|
32617
|
+
assetsDir: join13(arcDir, "assets"),
|
|
32618
|
+
publicDir: join13(rootDir, "public"),
|
|
32487
32619
|
packages,
|
|
32488
32620
|
manifest
|
|
32489
32621
|
};
|
|
@@ -32492,73 +32624,120 @@ async function buildAll(ws, opts = {}) {
|
|
|
32492
32624
|
const cache = loadBuildCache(ws.arcDir);
|
|
32493
32625
|
const noCache = opts.noCache ?? false;
|
|
32494
32626
|
const themePath = ws.rootPkg.arc?.theme;
|
|
32627
|
+
const federation = getFederationConfig(ws);
|
|
32495
32628
|
log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
32496
32629
|
assertOneModulePerPackage(ws.packages);
|
|
32497
32630
|
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
32498
|
-
const serverDir =
|
|
32631
|
+
const serverDir = join13(ws.arcDir, "server");
|
|
32499
32632
|
const { entryFile: serverEntry, externals: serverExternals } = await buildServerApp(ws.rootDir, serverDir, ws.packages, cache, noCache);
|
|
32500
|
-
const accessMap = await extractAccessMap(ws.rootDir,
|
|
32501
|
-
|
|
32502
|
-
|
|
32633
|
+
const accessMap = await extractAccessMap(ws.rootDir, join13(serverDir, serverEntry));
|
|
32634
|
+
mkdirSync10(ws.arcDir, { recursive: true });
|
|
32635
|
+
writeFileSync10(join13(ws.arcDir, "access.json"), JSON.stringify(accessMap.access, null, 2) + `
|
|
32503
32636
|
`);
|
|
32504
|
-
const
|
|
32637
|
+
const isHost = Boolean(federation?.host);
|
|
32638
|
+
const moduleNameOf2 = (name) => name.split("/").pop() ?? name;
|
|
32639
|
+
const exposureOf = (pkgName) => accessMap.exposure[moduleNameOf2(pkgName)]?.exposure ?? "local";
|
|
32640
|
+
const localPackages = isHost ? ws.packages : ws.packages.filter((p) => exposureOf(p.name) !== "external");
|
|
32641
|
+
const fedPackages = ws.packages.filter((p) => ["external", "both"].includes(exposureOf(p.name)));
|
|
32642
|
+
const hasFederatedModules = fedPackages.length > 0;
|
|
32643
|
+
const needsPeerShell = isHost || hasFederatedModules;
|
|
32644
|
+
log2();
|
|
32645
|
+
const plan = planChunks(localPackages, accessMap.access);
|
|
32505
32646
|
ok(`Chunks: ${plan.chunks.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`).join(", ")}`);
|
|
32647
|
+
const fedPlan = hasFederatedModules ? planChunks(fedPackages, accessMap.access) : null;
|
|
32648
|
+
if (fedPlan) {
|
|
32649
|
+
ok(`Federated modules: ${fedPackages.map((p) => moduleNameOf2(p.name)).join(", ")}`);
|
|
32650
|
+
}
|
|
32506
32651
|
const i18nCollector = new Map;
|
|
32507
|
-
const [browserResult] = await Promise.all([
|
|
32508
|
-
buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector
|
|
32652
|
+
const [browserResult, , , , shellResult, fedResult] = await Promise.all([
|
|
32653
|
+
buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector, {
|
|
32654
|
+
federated: false,
|
|
32655
|
+
exposeRuntime: isHost
|
|
32656
|
+
}),
|
|
32509
32657
|
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
32510
32658
|
copyBrowserAssets(ws, cache, noCache),
|
|
32511
|
-
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache)
|
|
32659
|
+
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
|
|
32660
|
+
needsPeerShell ? buildPeerShell(ws.rootDir, join13(ws.arcDir, "shell"), cache, noCache) : Promise.resolve(undefined),
|
|
32661
|
+
fedPlan ? buildBrowserApp(ws.rootDir, join13(ws.arcDir, "browser-fed"), fedPlan, cache, noCache, i18nCollector, { federated: true, unitId: "browser-app-fed" }) : Promise.resolve(undefined)
|
|
32512
32662
|
]);
|
|
32513
32663
|
const { finalizeTranslations: finalizeTranslations3 } = await Promise.resolve().then(() => (init_i18n(), exports_i18n));
|
|
32514
32664
|
await finalizeTranslations3(ws.rootDir, ws.arcDir, i18nCollector);
|
|
32515
32665
|
collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages, [], serverExternals);
|
|
32516
32666
|
saveBuildCache(ws.arcDir, cache);
|
|
32517
|
-
const
|
|
32518
|
-
|
|
32667
|
+
const federationBuild = fedResult && hasFederatedModules ? {
|
|
32668
|
+
config: {
|
|
32669
|
+
slug: federation?.slug,
|
|
32670
|
+
auth: federation?.auth
|
|
32671
|
+
},
|
|
32672
|
+
modules: fedPackages.map((p) => moduleNameOf2(p.name)),
|
|
32673
|
+
permissions: [
|
|
32674
|
+
...new Set(fedPackages.flatMap((p) => accessMap.exposure[moduleNameOf2(p.name)]?.permissions ?? []))
|
|
32675
|
+
],
|
|
32676
|
+
slots: [
|
|
32677
|
+
...new Set(fedPackages.flatMap((p) => accessMap.exposure[moduleNameOf2(p.name)]?.slots ?? []))
|
|
32678
|
+
],
|
|
32679
|
+
build: {
|
|
32680
|
+
initial: fedResult.initial,
|
|
32681
|
+
groups: fedResult.groups,
|
|
32682
|
+
sharedChunks: fedResult.sharedChunks
|
|
32683
|
+
}
|
|
32684
|
+
} : undefined;
|
|
32685
|
+
const finalManifest = assembleManifest(ws, browserResult, cache, shellResult, federationBuild);
|
|
32686
|
+
writeFileSync10(join13(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
|
|
32519
32687
|
return finalManifest;
|
|
32520
32688
|
}
|
|
32521
|
-
function assembleManifest(ws, browser, cache) {
|
|
32689
|
+
function assembleManifest(ws, browser, cache, shell, federation) {
|
|
32522
32690
|
const stylesHash = cache.units["styles"]?.outputHash ?? "";
|
|
32523
32691
|
return {
|
|
32524
32692
|
initial: browser.initial,
|
|
32525
32693
|
groups: browser.groups,
|
|
32526
32694
|
sharedChunks: browser.sharedChunks,
|
|
32527
32695
|
stylesHash,
|
|
32528
|
-
buildTime: new Date().toISOString()
|
|
32696
|
+
buildTime: new Date().toISOString(),
|
|
32697
|
+
...shell ? { shell: { imports: shell.imports, peers: shell.peers } } : {},
|
|
32698
|
+
...federation ? { federation } : {}
|
|
32529
32699
|
};
|
|
32530
32700
|
}
|
|
32701
|
+
function getFederationConfig(ws) {
|
|
32702
|
+
const raw = ws.rootPkg.arc?.federation;
|
|
32703
|
+
if (!raw)
|
|
32704
|
+
return;
|
|
32705
|
+
if (raw.slug && !/^[a-z0-9-]+$/.test(raw.slug)) {
|
|
32706
|
+
throw new Error(`arc.federation.slug "${raw.slug}" \u2014 dozwolone tylko [a-z0-9-]+`);
|
|
32707
|
+
}
|
|
32708
|
+
return raw;
|
|
32709
|
+
}
|
|
32531
32710
|
function resolveAssetSource(from, pkgDir, rootDir) {
|
|
32532
32711
|
if (from.startsWith("./") || from.startsWith("../")) {
|
|
32533
|
-
const resolved =
|
|
32534
|
-
return
|
|
32712
|
+
const resolved = join13(pkgDir, from);
|
|
32713
|
+
return existsSync11(resolved) ? resolved : null;
|
|
32535
32714
|
}
|
|
32536
32715
|
const candidates = [
|
|
32537
|
-
|
|
32538
|
-
|
|
32716
|
+
join13(rootDir, "node_modules", from),
|
|
32717
|
+
join13(pkgDir, "node_modules", from)
|
|
32539
32718
|
];
|
|
32540
32719
|
for (const c of candidates) {
|
|
32541
|
-
if (
|
|
32720
|
+
if (existsSync11(c))
|
|
32542
32721
|
return c;
|
|
32543
32722
|
}
|
|
32544
|
-
const bunCacheDir =
|
|
32545
|
-
if (
|
|
32723
|
+
const bunCacheDir = join13(rootDir, "node_modules", ".bun");
|
|
32724
|
+
if (existsSync11(bunCacheDir)) {
|
|
32546
32725
|
for (const entry of readdirSync6(bunCacheDir, { withFileTypes: true })) {
|
|
32547
32726
|
if (!entry.isDirectory())
|
|
32548
32727
|
continue;
|
|
32549
|
-
const candidate =
|
|
32550
|
-
if (
|
|
32728
|
+
const candidate = join13(bunCacheDir, entry.name, "node_modules", from);
|
|
32729
|
+
if (existsSync11(candidate))
|
|
32551
32730
|
return candidate;
|
|
32552
32731
|
}
|
|
32553
32732
|
}
|
|
32554
32733
|
return null;
|
|
32555
32734
|
}
|
|
32556
32735
|
function readBrowserAssets(pkgDir) {
|
|
32557
|
-
const pkgJsonPath =
|
|
32558
|
-
if (!
|
|
32736
|
+
const pkgJsonPath = join13(pkgDir, "package.json");
|
|
32737
|
+
if (!existsSync11(pkgJsonPath))
|
|
32559
32738
|
return [];
|
|
32560
32739
|
try {
|
|
32561
|
-
const pkg = JSON.parse(
|
|
32740
|
+
const pkg = JSON.parse(readFileSync12(pkgJsonPath, "utf-8"));
|
|
32562
32741
|
const assets = pkg.arc?.browserAssets;
|
|
32563
32742
|
if (!Array.isArray(assets))
|
|
32564
32743
|
return [];
|
|
@@ -32568,14 +32747,14 @@ function readBrowserAssets(pkgDir) {
|
|
|
32568
32747
|
}
|
|
32569
32748
|
}
|
|
32570
32749
|
function discoverBrowserAssets(ws) {
|
|
32571
|
-
const arcDir =
|
|
32572
|
-
if (!
|
|
32750
|
+
const arcDir = join13(ws.rootDir, "node_modules", "@arcote.tech");
|
|
32751
|
+
if (!existsSync11(arcDir))
|
|
32573
32752
|
return [];
|
|
32574
32753
|
const out = [];
|
|
32575
32754
|
for (const entry of readdirSync6(arcDir, { withFileTypes: true })) {
|
|
32576
32755
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
32577
32756
|
continue;
|
|
32578
|
-
const pkgDir =
|
|
32757
|
+
const pkgDir = join13(arcDir, entry.name);
|
|
32579
32758
|
const assets = readBrowserAssets(pkgDir);
|
|
32580
32759
|
for (const asset of assets) {
|
|
32581
32760
|
const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
|
|
@@ -32589,7 +32768,7 @@ function discoverBrowserAssets(ws) {
|
|
|
32589
32768
|
return out;
|
|
32590
32769
|
}
|
|
32591
32770
|
async function copyBrowserAssets(ws, cache, noCache) {
|
|
32592
|
-
|
|
32771
|
+
mkdirSync10(ws.assetsDir, { recursive: true });
|
|
32593
32772
|
const assets = discoverBrowserAssets(ws);
|
|
32594
32773
|
if (assets.length === 0)
|
|
32595
32774
|
return;
|
|
@@ -32600,7 +32779,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
32600
32779
|
to: a.to,
|
|
32601
32780
|
mtime: mtimeOf(a.src)
|
|
32602
32781
|
})));
|
|
32603
|
-
const requiredOutputs = assets.map((a) =>
|
|
32782
|
+
const requiredOutputs = assets.map((a) => join13(ws.assetsDir, a.to));
|
|
32604
32783
|
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
32605
32784
|
console.log(` \u2713 cached: browser-assets (${assets.length})`);
|
|
32606
32785
|
return;
|
|
@@ -32608,10 +32787,10 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
32608
32787
|
console.log(` building: browser-assets (${assets.length})`);
|
|
32609
32788
|
const outputHashes = {};
|
|
32610
32789
|
for (const asset of assets) {
|
|
32611
|
-
const dest =
|
|
32612
|
-
|
|
32790
|
+
const dest = join13(ws.assetsDir, asset.to);
|
|
32791
|
+
mkdirSync10(dirname8(dest), { recursive: true });
|
|
32613
32792
|
copyFileSync(asset.src, dest);
|
|
32614
|
-
outputHashes[asset.to] = sha256Hex(
|
|
32793
|
+
outputHashes[asset.to] = sha256Hex(readFileSync12(dest));
|
|
32615
32794
|
}
|
|
32616
32795
|
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
32617
32796
|
}
|
|
@@ -32619,12 +32798,12 @@ async function loadServerContext(ws) {
|
|
|
32619
32798
|
globalThis.ONLY_SERVER = true;
|
|
32620
32799
|
globalThis.ONLY_BROWSER = false;
|
|
32621
32800
|
globalThis.ONLY_CLIENT = false;
|
|
32622
|
-
const platformDir =
|
|
32623
|
-
const platformPkg = JSON.parse(
|
|
32624
|
-
const platformEntry =
|
|
32801
|
+
const platformDir = join13(process.cwd(), "node_modules", "@arcote.tech", "platform");
|
|
32802
|
+
const platformPkg = JSON.parse(readFileSync12(join13(platformDir, "package.json"), "utf-8"));
|
|
32803
|
+
const platformEntry = join13(platformDir, platformPkg.main ?? "src/index.ts");
|
|
32625
32804
|
await import(platformEntry);
|
|
32626
|
-
const serverEntry =
|
|
32627
|
-
if (!
|
|
32805
|
+
const serverEntry = join13(ws.arcDir, "server", SERVER_ENTRY_FILE);
|
|
32806
|
+
if (!existsSync11(serverEntry)) {
|
|
32628
32807
|
return { context: null, moduleAccess: new Map };
|
|
32629
32808
|
}
|
|
32630
32809
|
try {
|
|
@@ -32648,24 +32827,26 @@ async function platformBuild(opts = {}) {
|
|
|
32648
32827
|
}
|
|
32649
32828
|
|
|
32650
32829
|
// src/commands/platform-deploy.ts
|
|
32651
|
-
import {
|
|
32652
|
-
import {
|
|
32830
|
+
import { randomBytes } from "crypto";
|
|
32831
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
32832
|
+
import { dirname as dirname11, join as join21 } from "path";
|
|
32653
32833
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
32654
32834
|
|
|
32655
32835
|
// src/deploy/bootstrap.ts
|
|
32656
32836
|
var {spawn: spawn4 } = globalThis.Bun;
|
|
32657
|
-
import { mkdirSync as
|
|
32837
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
|
|
32658
32838
|
import { tmpdir as tmpdir2 } from "os";
|
|
32659
|
-
import { dirname as dirname9, join as
|
|
32839
|
+
import { dirname as dirname9, join as join19 } from "path";
|
|
32660
32840
|
|
|
32661
32841
|
// src/deploy/ansible.ts
|
|
32662
32842
|
import { spawn as nodeSpawn } from "child_process";
|
|
32663
|
-
import { existsSync as
|
|
32843
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
32664
32844
|
import { homedir, tmpdir } from "os";
|
|
32665
|
-
import { join as
|
|
32845
|
+
import { join as join14 } from "path";
|
|
32666
32846
|
|
|
32667
32847
|
// src/deploy/assets.ts
|
|
32668
32848
|
var TERRAFORM_MAIN_TF = `terraform {
|
|
32849
|
+
required_version = ">= 1.1"
|
|
32669
32850
|
required_providers {
|
|
32670
32851
|
hcloud = {
|
|
32671
32852
|
source = "hetznercloud/hcloud"
|
|
@@ -32678,17 +32859,50 @@ provider "hcloud" {
|
|
|
32678
32859
|
token = var.hcloud_token
|
|
32679
32860
|
}
|
|
32680
32861
|
|
|
32862
|
+
locals {
|
|
32863
|
+
# cloud-init injects the operator public key into the default image user's
|
|
32864
|
+
# authorized_keys (root on Hetzner Ubuntu images) \u2014 matching the root-first
|
|
32865
|
+
# ansible bootstrap. Used only when use_cloud_init_key = true.
|
|
32866
|
+
cloud_init_user_data = <<-EOT
|
|
32867
|
+
#cloud-config
|
|
32868
|
+
ssh_pwauth: false
|
|
32869
|
+
chpasswd:
|
|
32870
|
+
expire: false
|
|
32871
|
+
ssh_authorized_keys:
|
|
32872
|
+
- \${trimspace(file(var.ssh_public_key))}
|
|
32873
|
+
# Hetzner marks the root password expired (must-change-on-login) when no
|
|
32874
|
+
# hcloud_ssh_key object is attached \u2014 which blocks non-interactive key
|
|
32875
|
+
# login ("password change required, no TTY"). Clear the expiry so the
|
|
32876
|
+
# deploy user / ansible can SSH in as root right away.
|
|
32877
|
+
runcmd:
|
|
32878
|
+
- chage -d $(date +%Y-%m-%d) -M -1 root
|
|
32879
|
+
EOT
|
|
32880
|
+
}
|
|
32881
|
+
|
|
32882
|
+
# Legacy path: a managed SSH key object in the Hetzner project. Skipped when
|
|
32883
|
+
# use_cloud_init_key = true, because several apps sharing one project with the
|
|
32884
|
+
# same local public key would collide on both key NAME and FINGERPRINT.
|
|
32681
32885
|
resource "hcloud_ssh_key" "deploy" {
|
|
32886
|
+
count = var.use_cloud_init_key ? 0 : 1
|
|
32682
32887
|
name = "\${var.server_name}-deploy"
|
|
32683
32888
|
public_key = file(var.ssh_public_key)
|
|
32684
32889
|
}
|
|
32685
32890
|
|
|
32891
|
+
# Re-home the pre-count address so an already-provisioned host (state created
|
|
32892
|
+
# before count existed) plans as a no-op instead of destroy+recreate \u2014 which
|
|
32893
|
+
# would ForceNew-replace the server. Ignored for fresh (empty) state.
|
|
32894
|
+
moved {
|
|
32895
|
+
from = hcloud_ssh_key.deploy
|
|
32896
|
+
to = hcloud_ssh_key.deploy[0]
|
|
32897
|
+
}
|
|
32898
|
+
|
|
32686
32899
|
resource "hcloud_server" "arc" {
|
|
32687
32900
|
name = var.server_name
|
|
32688
32901
|
image = var.server_image
|
|
32689
32902
|
server_type = var.server_type
|
|
32690
32903
|
location = var.server_location
|
|
32691
|
-
ssh_keys = [hcloud_ssh_key.deploy.id]
|
|
32904
|
+
ssh_keys = var.use_cloud_init_key ? [] : [hcloud_ssh_key.deploy[0].id]
|
|
32905
|
+
user_data = var.use_cloud_init_key ? local.cloud_init_user_data : null
|
|
32692
32906
|
|
|
32693
32907
|
public_net {
|
|
32694
32908
|
ipv4_enabled = true
|
|
@@ -32739,6 +32953,12 @@ variable "ssh_public_key" {
|
|
|
32739
32953
|
type = string
|
|
32740
32954
|
default = "~/.ssh/id_ed25519.pub"
|
|
32741
32955
|
}
|
|
32956
|
+
|
|
32957
|
+
variable "use_cloud_init_key" {
|
|
32958
|
+
description = "Inject the SSH public key via cloud-init user_data instead of a managed hcloud_ssh_key object (avoids name/fingerprint collisions when multiple apps share one Hetzner project)."
|
|
32959
|
+
type = bool
|
|
32960
|
+
default = false
|
|
32961
|
+
}
|
|
32742
32962
|
`;
|
|
32743
32963
|
var ANSIBLE_SITE_YML = `---
|
|
32744
32964
|
# Arc platform bootstrap playbook \u2014 minimal hardened Docker host.
|
|
@@ -32935,30 +33155,30 @@ var ASSETS = {
|
|
|
32935
33155
|
}
|
|
32936
33156
|
};
|
|
32937
33157
|
async function materializeAssets(targetDir, files) {
|
|
32938
|
-
const { mkdirSync:
|
|
32939
|
-
const { join:
|
|
32940
|
-
|
|
33158
|
+
const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync11 } = await import("fs");
|
|
33159
|
+
const { join: join14 } = await import("path");
|
|
33160
|
+
mkdirSync11(targetDir, { recursive: true });
|
|
32941
33161
|
for (const [name, content] of Object.entries(files)) {
|
|
32942
|
-
|
|
33162
|
+
writeFileSync11(join14(targetDir, name), content);
|
|
32943
33163
|
}
|
|
32944
33164
|
}
|
|
32945
33165
|
|
|
32946
33166
|
// src/deploy/ansible.ts
|
|
32947
33167
|
function pickSshKeyForAnsible(configured) {
|
|
32948
33168
|
if (configured) {
|
|
32949
|
-
const expanded = configured.startsWith("~") ?
|
|
32950
|
-
return
|
|
33169
|
+
const expanded = configured.startsWith("~") ? join14(homedir(), configured.slice(1)) : configured;
|
|
33170
|
+
return existsSync12(expanded) ? expanded : null;
|
|
32951
33171
|
}
|
|
32952
33172
|
for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
|
|
32953
|
-
const path4 =
|
|
32954
|
-
if (
|
|
33173
|
+
const path4 = join14(homedir(), ".ssh", name);
|
|
33174
|
+
if (existsSync12(path4))
|
|
32955
33175
|
return path4;
|
|
32956
33176
|
}
|
|
32957
33177
|
return null;
|
|
32958
33178
|
}
|
|
32959
33179
|
async function runAnsible(inputs) {
|
|
32960
|
-
const workDir =
|
|
32961
|
-
|
|
33180
|
+
const workDir = join14(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
|
|
33181
|
+
mkdirSync11(workDir, { recursive: true });
|
|
32962
33182
|
await materializeAssets(workDir, ASSETS.ansible);
|
|
32963
33183
|
const user = inputs.asRoot ? "root" : inputs.target.user;
|
|
32964
33184
|
const port = inputs.ansible?.sshPort ?? inputs.target.port;
|
|
@@ -32974,7 +33194,7 @@ async function runAnsible(inputs) {
|
|
|
32974
33194
|
""
|
|
32975
33195
|
].join(`
|
|
32976
33196
|
`);
|
|
32977
|
-
|
|
33197
|
+
writeFileSync11(join14(workDir, "inventory.ini"), inventory);
|
|
32978
33198
|
const extraVarsJson = JSON.stringify({
|
|
32979
33199
|
username: inputs.target.user,
|
|
32980
33200
|
ssh_port: port,
|
|
@@ -34533,16 +34753,16 @@ function panelTimeseries(title, gridPos, query, legend, unit, datasource = PROME
|
|
|
34533
34753
|
// src/deploy/terraform.ts
|
|
34534
34754
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
34535
34755
|
import { createHash as createHash2 } from "crypto";
|
|
34536
|
-
import { existsSync as
|
|
34756
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
34537
34757
|
import { homedir as homedir2 } from "os";
|
|
34538
|
-
import { join as
|
|
34758
|
+
import { join as join15 } from "path";
|
|
34539
34759
|
async function runTerraform(inputs) {
|
|
34540
34760
|
const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
|
|
34541
|
-
const workDir =
|
|
34542
|
-
|
|
34761
|
+
const workDir = join15(homedir2(), ".arc-deploy", wsHash, "tf");
|
|
34762
|
+
mkdirSync12(workDir, { recursive: true });
|
|
34543
34763
|
await materializeAssets(workDir, ASSETS.terraform);
|
|
34544
34764
|
const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
|
|
34545
|
-
if (!
|
|
34765
|
+
if (!existsSync13(expandHome(sshPubKey))) {
|
|
34546
34766
|
throw new Error(`SSH public key not found at ${sshPubKey}. Set provision.terraform.sshPublicKey in deploy.arc.json.`);
|
|
34547
34767
|
}
|
|
34548
34768
|
const tfvars = [
|
|
@@ -34551,11 +34771,12 @@ async function runTerraform(inputs) {
|
|
|
34551
34771
|
`server_type = "${inputs.tf.serverType}"`,
|
|
34552
34772
|
`server_location = "${inputs.tf.location}"`,
|
|
34553
34773
|
`server_image = "${inputs.tf.image}"`,
|
|
34554
|
-
`ssh_public_key = "${expandHome(sshPubKey)}"
|
|
34774
|
+
`ssh_public_key = "${expandHome(sshPubKey)}"`,
|
|
34775
|
+
`use_cloud_init_key = ${inputs.useCloudInitKey}`
|
|
34555
34776
|
].join(`
|
|
34556
34777
|
`) + `
|
|
34557
34778
|
`;
|
|
34558
|
-
|
|
34779
|
+
writeFileSync12(join15(workDir, "terraform.tfvars"), tfvars);
|
|
34559
34780
|
await runTf(workDir, ["init", "-input=false", "-no-color"]);
|
|
34560
34781
|
await runTf(workDir, [
|
|
34561
34782
|
"apply",
|
|
@@ -34613,20 +34834,20 @@ function expandHome(p) {
|
|
|
34613
34834
|
}
|
|
34614
34835
|
|
|
34615
34836
|
// src/deploy/config.ts
|
|
34616
|
-
import { existsSync as
|
|
34617
|
-
import { join as
|
|
34837
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "fs";
|
|
34838
|
+
import { join as join17 } from "path";
|
|
34618
34839
|
|
|
34619
34840
|
// src/deploy/env-file.ts
|
|
34620
|
-
import { appendFileSync, existsSync as
|
|
34621
|
-
import { join as
|
|
34841
|
+
import { appendFileSync, existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
34842
|
+
import { join as join16 } from "path";
|
|
34622
34843
|
function loadDeployEnvFiles(rootDir, envNames) {
|
|
34623
|
-
const globalsPath =
|
|
34624
|
-
const globals =
|
|
34844
|
+
const globalsPath = join16(rootDir, "deploy.arc.env");
|
|
34845
|
+
const globals = existsSync14(globalsPath) ? parseEnvFile(readFileSync13(globalsPath, "utf-8"), globalsPath) : {};
|
|
34625
34846
|
const perEnv = {};
|
|
34626
34847
|
for (const name of envNames) {
|
|
34627
|
-
const envPath =
|
|
34628
|
-
if (
|
|
34629
|
-
perEnv[name] = parseEnvFile(
|
|
34848
|
+
const envPath = join16(rootDir, `deploy.arc.${name}.env`);
|
|
34849
|
+
if (existsSync14(envPath)) {
|
|
34850
|
+
perEnv[name] = parseEnvFile(readFileSync13(envPath, "utf-8"), envPath);
|
|
34630
34851
|
}
|
|
34631
34852
|
}
|
|
34632
34853
|
return { globals, perEnv };
|
|
@@ -34642,16 +34863,16 @@ function ensurePersistedSecret(rootDir, scope, key, generate) {
|
|
|
34642
34863
|
if (process.env[key])
|
|
34643
34864
|
return process.env[key];
|
|
34644
34865
|
const fileName = scope === "globals" ? "deploy.arc.env" : `deploy.arc.${scope}.env`;
|
|
34645
|
-
const path4 =
|
|
34646
|
-
if (
|
|
34647
|
-
const existing = parseEnvFile(
|
|
34866
|
+
const path4 = join16(rootDir, fileName);
|
|
34867
|
+
if (existsSync14(path4)) {
|
|
34868
|
+
const existing = parseEnvFile(readFileSync13(path4, "utf-8"), path4);
|
|
34648
34869
|
if (existing[key]) {
|
|
34649
34870
|
process.env[key] = existing[key];
|
|
34650
34871
|
return existing[key];
|
|
34651
34872
|
}
|
|
34652
34873
|
}
|
|
34653
34874
|
const value = generate();
|
|
34654
|
-
const prefix =
|
|
34875
|
+
const prefix = existsSync14(path4) && !readFileSync13(path4, "utf-8").endsWith(`
|
|
34655
34876
|
`) ? `
|
|
34656
34877
|
` : "";
|
|
34657
34878
|
appendFileSync(path4, `${prefix}${key}=${value}
|
|
@@ -34687,17 +34908,17 @@ function parseEnvFile(content, pathForErrors) {
|
|
|
34687
34908
|
// src/deploy/config.ts
|
|
34688
34909
|
var DEPLOY_CONFIG_FILE = "deploy.arc.json";
|
|
34689
34910
|
function deployConfigPath(rootDir) {
|
|
34690
|
-
return
|
|
34911
|
+
return join17(rootDir, DEPLOY_CONFIG_FILE);
|
|
34691
34912
|
}
|
|
34692
34913
|
function deployConfigExists(rootDir) {
|
|
34693
|
-
return
|
|
34914
|
+
return existsSync15(deployConfigPath(rootDir));
|
|
34694
34915
|
}
|
|
34695
34916
|
function loadDeployConfig(rootDir) {
|
|
34696
34917
|
const path4 = deployConfigPath(rootDir);
|
|
34697
|
-
if (!
|
|
34918
|
+
if (!existsSync15(path4)) {
|
|
34698
34919
|
throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
|
|
34699
34920
|
}
|
|
34700
|
-
const raw =
|
|
34921
|
+
const raw = readFileSync14(path4, "utf-8");
|
|
34701
34922
|
let parsed;
|
|
34702
34923
|
try {
|
|
34703
34924
|
parsed = JSON.parse(raw);
|
|
@@ -34720,9 +34941,9 @@ function loadDeployConfig(rootDir) {
|
|
|
34720
34941
|
}
|
|
34721
34942
|
function saveDeployConfig(rootDir, cfg) {
|
|
34722
34943
|
const path4 = deployConfigPath(rootDir);
|
|
34723
|
-
const raw =
|
|
34944
|
+
const raw = existsSync15(path4) ? JSON.parse(readFileSync14(path4, "utf-8")) : {};
|
|
34724
34945
|
raw.target = { ...raw.target, ...cfg.target };
|
|
34725
|
-
|
|
34946
|
+
writeFileSync13(path4, JSON.stringify(raw, null, 2) + `
|
|
34726
34947
|
`);
|
|
34727
34948
|
}
|
|
34728
34949
|
var VAR_REGEX = /\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g;
|
|
@@ -34757,9 +34978,20 @@ function validateDeployConfig(input) {
|
|
|
34757
34978
|
if (!/^[A-Z][A-Z0-9_]*$/.test(passwordEnv)) {
|
|
34758
34979
|
throw new Error(`deploy.arc.json: registry.passwordEnv "${passwordEnv}" must be an UPPER_SNAKE_CASE env var name`);
|
|
34759
34980
|
}
|
|
34981
|
+
const provisionRaw = input.provision;
|
|
34982
|
+
const hasProvisionTf = isObject(provisionRaw) && isObject(provisionRaw.terraform);
|
|
34983
|
+
const hostRaw = optionalString(target, "target.host");
|
|
34984
|
+
let host;
|
|
34985
|
+
if (hostRaw && hostRaw.length > 0) {
|
|
34986
|
+
host = hostRaw;
|
|
34987
|
+
} else if (hasProvisionTf) {
|
|
34988
|
+
host = "PENDING_TERRAFORM";
|
|
34989
|
+
} else {
|
|
34990
|
+
throw new Error(`deploy.arc.json: target.host is required unless provision.terraform is present (with provision, terraform fills it on first deploy).`);
|
|
34991
|
+
}
|
|
34760
34992
|
const validated = {
|
|
34761
34993
|
target: {
|
|
34762
|
-
host
|
|
34994
|
+
host,
|
|
34763
34995
|
user: optionalString(target, "target.user") ?? "deploy",
|
|
34764
34996
|
port: optionalNumber(target, "target.port") ?? 22,
|
|
34765
34997
|
remoteDir: optionalString(target, "target.remoteDir") ?? "/opt/arc",
|
|
@@ -34860,13 +35092,23 @@ function validateDeployConfig(input) {
|
|
|
34860
35092
|
if (providerVal !== "hcloud") {
|
|
34861
35093
|
throw new Error(`deploy.arc.json: provision.terraform.provider must be "hcloud" (got "${providerVal}")`);
|
|
34862
35094
|
}
|
|
35095
|
+
const serverName = optionalString(tf, "provision.terraform.serverName");
|
|
35096
|
+
if (serverName !== undefined && !/^[a-z0-9-]{1,63}$/.test(serverName)) {
|
|
35097
|
+
throw new Error(`deploy.arc.json: provision.terraform.serverName "${serverName}" must match [a-z0-9-] and be at most 63 chars`);
|
|
35098
|
+
}
|
|
35099
|
+
const sshKeyStrategyRaw = optionalString(tf, "provision.terraform.sshKeyStrategy");
|
|
35100
|
+
if (sshKeyStrategyRaw !== undefined && sshKeyStrategyRaw !== "resource" && sshKeyStrategyRaw !== "cloud-init") {
|
|
35101
|
+
throw new Error(`deploy.arc.json: provision.terraform.sshKeyStrategy must be "resource" or "cloud-init" (got "${sshKeyStrategyRaw}")`);
|
|
35102
|
+
}
|
|
34863
35103
|
const terraform = {
|
|
34864
35104
|
provider: "hcloud",
|
|
34865
35105
|
serverType: requireString(tf, "provision.terraform.serverType"),
|
|
34866
35106
|
location: requireString(tf, "provision.terraform.location"),
|
|
34867
35107
|
image: optionalString(tf, "provision.terraform.image") ?? "ubuntu-22.04",
|
|
34868
35108
|
tokenEnv: requireString(tf, "provision.terraform.tokenEnv"),
|
|
34869
|
-
sshPublicKey: optionalString(tf, "provision.terraform.sshPublicKey")
|
|
35109
|
+
sshPublicKey: optionalString(tf, "provision.terraform.sshPublicKey"),
|
|
35110
|
+
serverName,
|
|
35111
|
+
sshKeyStrategy: sshKeyStrategyRaw
|
|
34870
35112
|
};
|
|
34871
35113
|
let ansible;
|
|
34872
35114
|
const ansibleRaw = provision.ansible;
|
|
@@ -34936,17 +35178,17 @@ function cfgErr(path4, expected) {
|
|
|
34936
35178
|
|
|
34937
35179
|
// src/deploy/ssh.ts
|
|
34938
35180
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
34939
|
-
import { existsSync as
|
|
35181
|
+
import { existsSync as existsSync16 } from "fs";
|
|
34940
35182
|
import { homedir as homedir3 } from "os";
|
|
34941
|
-
import { join as
|
|
35183
|
+
import { join as join18 } from "path";
|
|
34942
35184
|
function pickSshKey(target) {
|
|
34943
35185
|
if (target.sshKey) {
|
|
34944
|
-
const expanded = target.sshKey.startsWith("~") ?
|
|
34945
|
-
return
|
|
35186
|
+
const expanded = target.sshKey.startsWith("~") ? join18(homedir3(), target.sshKey.slice(1)) : target.sshKey;
|
|
35187
|
+
return existsSync16(expanded) ? expanded : null;
|
|
34946
35188
|
}
|
|
34947
35189
|
for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
|
|
34948
|
-
const path4 =
|
|
34949
|
-
if (
|
|
35190
|
+
const path4 = join18(homedir3(), ".ssh", name);
|
|
35191
|
+
if (existsSync16(path4))
|
|
34950
35192
|
return path4;
|
|
34951
35193
|
}
|
|
34952
35194
|
return null;
|
|
@@ -34961,7 +35203,7 @@ function sshMuxArgs() {
|
|
|
34961
35203
|
"-o",
|
|
34962
35204
|
"ControlMaster=auto",
|
|
34963
35205
|
"-o",
|
|
34964
|
-
`ControlPath=${
|
|
35206
|
+
`ControlPath=${join18(homedir3(), ".ssh", "cm-arc-%C")}`,
|
|
34965
35207
|
"-o",
|
|
34966
35208
|
"ControlPersist=120"
|
|
34967
35209
|
];
|
|
@@ -35161,10 +35403,14 @@ async function bootstrap(inputs) {
|
|
|
35161
35403
|
if (!token) {
|
|
35162
35404
|
throw new Error(`Environment variable ${cfg.provision.terraform.tokenEnv} is not set`);
|
|
35163
35405
|
}
|
|
35406
|
+
const firstEnv = Object.keys(cfg.envs)[0] ?? "host";
|
|
35407
|
+
const serverName = cfg.provision.terraform.serverName ?? `arc-${firstEnv}`;
|
|
35408
|
+
const useCloudInitKey = cfg.provision.terraform.sshKeyStrategy === "cloud-init";
|
|
35164
35409
|
const tfOut = await runTerraform({
|
|
35165
35410
|
tf: cfg.provision.terraform,
|
|
35166
35411
|
token,
|
|
35167
|
-
serverName
|
|
35412
|
+
serverName,
|
|
35413
|
+
useCloudInitKey,
|
|
35168
35414
|
workspaceDir: rootDir
|
|
35169
35415
|
});
|
|
35170
35416
|
ok(`Server provisioned: ${tfOut.serverIp}`);
|
|
@@ -35226,17 +35472,17 @@ async function isRegistryRunning(cfg) {
|
|
|
35226
35472
|
}
|
|
35227
35473
|
async function upStack(inputs) {
|
|
35228
35474
|
const { cfg } = inputs;
|
|
35229
|
-
const workDir =
|
|
35230
|
-
|
|
35475
|
+
const workDir = join19(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
|
|
35476
|
+
mkdirSync13(workDir, { recursive: true });
|
|
35231
35477
|
await assertRegistryDnsResolves(cfg);
|
|
35232
35478
|
const password = process.env[cfg.registry.passwordEnv];
|
|
35233
35479
|
if (!password) {
|
|
35234
35480
|
throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set. ` + `Set it (e.g. \`export ${cfg.registry.passwordEnv}=...\`) before bootstrap.`);
|
|
35235
35481
|
}
|
|
35236
35482
|
const htpasswdLine = await generateHtpasswd(cfg.registry.username, password);
|
|
35237
|
-
|
|
35238
|
-
|
|
35239
|
-
|
|
35483
|
+
writeFileSync14(join19(workDir, "htpasswd"), htpasswdLine);
|
|
35484
|
+
writeFileSync14(join19(workDir, "Caddyfile"), generateCaddyfile(cfg));
|
|
35485
|
+
writeFileSync14(join19(workDir, "docker-compose.yml"), generateCompose({ cfg }));
|
|
35240
35486
|
let observabilityFiles = null;
|
|
35241
35487
|
let observabilityHtpasswd = null;
|
|
35242
35488
|
if (cfg.observability?.enabled) {
|
|
@@ -35248,30 +35494,30 @@ async function upStack(inputs) {
|
|
|
35248
35494
|
}
|
|
35249
35495
|
observabilityHtpasswd = await generateCaddyBasicAuthLine("admin", adminPassword);
|
|
35250
35496
|
for (const [relPath, contents] of Object.entries(observabilityFiles)) {
|
|
35251
|
-
const fullPath =
|
|
35252
|
-
|
|
35253
|
-
|
|
35497
|
+
const fullPath = join19(workDir, relPath);
|
|
35498
|
+
mkdirSync13(dirname9(fullPath), { recursive: true });
|
|
35499
|
+
writeFileSync14(fullPath, contents);
|
|
35254
35500
|
}
|
|
35255
|
-
|
|
35501
|
+
writeFileSync14(join19(workDir, "observability-htpasswd"), observabilityHtpasswd);
|
|
35256
35502
|
}
|
|
35257
35503
|
await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
|
|
35258
35504
|
for (const name of Object.keys(cfg.envs)) {
|
|
35259
35505
|
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
|
|
35260
35506
|
}
|
|
35261
35507
|
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/registry-auth`);
|
|
35262
|
-
await scpUpload(cfg.target,
|
|
35263
|
-
await scpUpload(cfg.target,
|
|
35264
|
-
await scpUpload(cfg.target,
|
|
35508
|
+
await scpUpload(cfg.target, join19(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
|
|
35509
|
+
await scpUpload(cfg.target, join19(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
|
|
35510
|
+
await scpUpload(cfg.target, join19(workDir, "htpasswd"), `${cfg.target.remoteDir}/registry-auth/htpasswd`);
|
|
35265
35511
|
if (observabilityFiles && observabilityHtpasswd) {
|
|
35266
35512
|
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/observability/grafana-dashboards ${cfg.target.remoteDir}/observability/grafana-alerting`);
|
|
35267
35513
|
for (const relPath of Object.keys(observabilityFiles)) {
|
|
35268
|
-
const localDir = dirname9(
|
|
35269
|
-
|
|
35514
|
+
const localDir = dirname9(join19(workDir, relPath));
|
|
35515
|
+
mkdirSync13(localDir, { recursive: true });
|
|
35270
35516
|
}
|
|
35271
35517
|
for (const relPath of Object.keys(observabilityFiles)) {
|
|
35272
|
-
await scpUpload(cfg.target,
|
|
35518
|
+
await scpUpload(cfg.target, join19(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
|
|
35273
35519
|
}
|
|
35274
|
-
await scpUpload(cfg.target,
|
|
35520
|
+
await scpUpload(cfg.target, join19(workDir, "observability-htpasswd"), `${cfg.target.remoteDir}/observability-htpasswd`);
|
|
35275
35521
|
}
|
|
35276
35522
|
await assertExec(cfg.target, `touch ${cfg.target.remoteDir}/.env`);
|
|
35277
35523
|
if (cfg.observability?.enabled) {
|
|
@@ -35426,14 +35672,14 @@ var {spawn: spawn5 } = globalThis.Bun;
|
|
|
35426
35672
|
import { createHash as createHash3 } from "crypto";
|
|
35427
35673
|
import {
|
|
35428
35674
|
copyFileSync as copyFileSync2,
|
|
35429
|
-
existsSync as
|
|
35430
|
-
mkdirSync as
|
|
35431
|
-
readFileSync as
|
|
35675
|
+
existsSync as existsSync17,
|
|
35676
|
+
mkdirSync as mkdirSync14,
|
|
35677
|
+
readFileSync as readFileSync15,
|
|
35432
35678
|
realpathSync as realpathSync2,
|
|
35433
|
-
writeFileSync as
|
|
35679
|
+
writeFileSync as writeFileSync15
|
|
35434
35680
|
} from "fs";
|
|
35435
35681
|
import { tmpdir as tmpdir3 } from "os";
|
|
35436
|
-
import { dirname as dirname10, join as
|
|
35682
|
+
import { dirname as dirname10, join as join20 } from "path";
|
|
35437
35683
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
35438
35684
|
|
|
35439
35685
|
// src/deploy/image-template.ts
|
|
@@ -35483,8 +35729,8 @@ function generateDockerfile(inputs) {
|
|
|
35483
35729
|
// src/deploy/image.ts
|
|
35484
35730
|
async function buildImage(ws, opts) {
|
|
35485
35731
|
await ensureDocker();
|
|
35486
|
-
const manifestPath =
|
|
35487
|
-
if (!
|
|
35732
|
+
const manifestPath = join20(ws.arcDir, "manifest.json");
|
|
35733
|
+
if (!existsSync17(manifestPath)) {
|
|
35488
35734
|
throw new Error(`No build manifest at ${manifestPath}. Run \`arc platform build\` first or omit --skip-build.`);
|
|
35489
35735
|
}
|
|
35490
35736
|
embedCliBundle(ws);
|
|
@@ -35494,10 +35740,10 @@ async function buildImage(ws, opts) {
|
|
|
35494
35740
|
const dockerfileInputs = collectDockerfileInputs(ws);
|
|
35495
35741
|
const dockerfile = generateDockerfile(dockerfileInputs);
|
|
35496
35742
|
const buildContextDir = ws.rootDir;
|
|
35497
|
-
const dockerfileDir =
|
|
35498
|
-
|
|
35499
|
-
const dockerfilePath =
|
|
35500
|
-
|
|
35743
|
+
const dockerfileDir = join20(tmpdir3(), `arc-image-${Date.now()}`);
|
|
35744
|
+
mkdirSync14(dockerfileDir, { recursive: true });
|
|
35745
|
+
const dockerfilePath = join20(dockerfileDir, "Dockerfile");
|
|
35746
|
+
writeFileSync15(dockerfilePath, dockerfile);
|
|
35501
35747
|
const buildArgs = [
|
|
35502
35748
|
"build",
|
|
35503
35749
|
"--platform=linux/amd64",
|
|
@@ -35522,20 +35768,20 @@ async function buildImage(ws, opts) {
|
|
|
35522
35768
|
}
|
|
35523
35769
|
function embedCliBundle(ws) {
|
|
35524
35770
|
const source = locateCliBundle();
|
|
35525
|
-
const target =
|
|
35771
|
+
const target = join20(ws.arcDir, "host.js");
|
|
35526
35772
|
copyFileSync2(source, target);
|
|
35527
35773
|
}
|
|
35528
35774
|
function locateCliBundle() {
|
|
35529
35775
|
const here = fileURLToPath7(import.meta.url);
|
|
35530
35776
|
let cur = dirname10(here);
|
|
35531
35777
|
while (cur !== "/" && cur !== "") {
|
|
35532
|
-
const candidate =
|
|
35533
|
-
if (
|
|
35778
|
+
const candidate = join20(cur, "package.json");
|
|
35779
|
+
if (existsSync17(candidate)) {
|
|
35534
35780
|
try {
|
|
35535
|
-
const pkg = JSON.parse(
|
|
35781
|
+
const pkg = JSON.parse(readFileSync15(candidate, "utf-8"));
|
|
35536
35782
|
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
35537
|
-
const distIndex =
|
|
35538
|
-
if (!
|
|
35783
|
+
const distIndex = join20(realpathSync2(cur), "dist", "index.js");
|
|
35784
|
+
if (!existsSync17(distIndex)) {
|
|
35539
35785
|
throw new Error(`arc-cli bundle missing at ${distIndex}. Run \`bun run build\` in packages/cli/.`);
|
|
35540
35786
|
}
|
|
35541
35787
|
return distIndex;
|
|
@@ -35567,17 +35813,17 @@ async function ensureDocker() {
|
|
|
35567
35813
|
}
|
|
35568
35814
|
}
|
|
35569
35815
|
function computeContentHash(manifestPath) {
|
|
35570
|
-
const raw = JSON.parse(
|
|
35816
|
+
const raw = JSON.parse(readFileSync15(manifestPath, "utf-8"));
|
|
35571
35817
|
delete raw.buildTime;
|
|
35572
35818
|
const canonical = JSON.stringify(raw);
|
|
35573
35819
|
return createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
35574
35820
|
}
|
|
35575
35821
|
function collectDockerfileInputs(ws) {
|
|
35576
|
-
const hasPublicDir =
|
|
35577
|
-
const hasLocales =
|
|
35822
|
+
const hasPublicDir = existsSync17(join20(ws.rootDir, "public"));
|
|
35823
|
+
const hasLocales = existsSync17(join20(ws.rootDir, "locales"));
|
|
35578
35824
|
let manifestPath;
|
|
35579
35825
|
for (const name of ["manifest.webmanifest", "manifest.json"]) {
|
|
35580
|
-
if (
|
|
35826
|
+
if (existsSync17(join20(ws.rootDir, name))) {
|
|
35581
35827
|
manifestPath = name;
|
|
35582
35828
|
break;
|
|
35583
35829
|
}
|
|
@@ -36192,7 +36438,7 @@ ${import_picocolors2.default.gray(m2)} ${s}
|
|
|
36192
36438
|
};
|
|
36193
36439
|
|
|
36194
36440
|
// src/deploy/survey.ts
|
|
36195
|
-
async function runSurvey() {
|
|
36441
|
+
async function runSurvey(appName) {
|
|
36196
36442
|
we("arc platform deploy \u2014 initial setup");
|
|
36197
36443
|
const mode = await de({
|
|
36198
36444
|
message: "How should deploy reach the target server?",
|
|
@@ -36294,12 +36540,17 @@ async function runSurvey() {
|
|
|
36294
36540
|
});
|
|
36295
36541
|
if (BD(location))
|
|
36296
36542
|
cancel();
|
|
36543
|
+
const firstEnv = Object.keys(envs)[0] ?? "host";
|
|
36544
|
+
const appSlug = sanitizeImageName(appName ?? "arc-app").replace(/[^a-z0-9-]+/g, "-");
|
|
36545
|
+
const serverName = `arc-${appSlug}-${firstEnv}`.slice(0, 63);
|
|
36297
36546
|
const terraform = {
|
|
36298
36547
|
provider: "hcloud",
|
|
36299
36548
|
serverType,
|
|
36300
36549
|
location,
|
|
36301
36550
|
image: "ubuntu-22.04",
|
|
36302
|
-
tokenEnv
|
|
36551
|
+
tokenEnv,
|
|
36552
|
+
serverName,
|
|
36553
|
+
sshKeyStrategy: "cloud-init"
|
|
36303
36554
|
};
|
|
36304
36555
|
provision = { terraform };
|
|
36305
36556
|
}
|
|
@@ -36384,12 +36635,15 @@ function cancel() {
|
|
|
36384
36635
|
}
|
|
36385
36636
|
|
|
36386
36637
|
// src/commands/platform-deploy.ts
|
|
36638
|
+
function federationExposesModules(ws) {
|
|
36639
|
+
return Boolean(ws.rootPkg?.arc?.federation?.slug);
|
|
36640
|
+
}
|
|
36387
36641
|
async function platformDeploy(envArg, options = {}) {
|
|
36388
36642
|
const ws = resolveWorkspace();
|
|
36389
36643
|
let cfg;
|
|
36390
36644
|
if (!deployConfigExists(ws.rootDir)) {
|
|
36391
36645
|
log2("No deploy.arc.json found \u2014 launching survey.");
|
|
36392
|
-
cfg = await runSurvey();
|
|
36646
|
+
cfg = await runSurvey(ws.appName);
|
|
36393
36647
|
saveDeployConfig(ws.rootDir, cfg);
|
|
36394
36648
|
ok("Saved deploy.arc.json");
|
|
36395
36649
|
}
|
|
@@ -36398,6 +36652,13 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
36398
36652
|
for (const pg of pgEnvs) {
|
|
36399
36653
|
ensurePersistedSecret(ws.rootDir, pg.name, pg.passwordKey, () => crypto.randomUUID().replace(/-/g, ""));
|
|
36400
36654
|
}
|
|
36655
|
+
if (federationExposesModules(ws)) {
|
|
36656
|
+
for (const name of Object.keys(cfg.envs)) {
|
|
36657
|
+
ensurePersistedSecret(ws.rootDir, name, "ARC_APP_SECRET", () => randomBytes(32).toString("base64url"));
|
|
36658
|
+
ensurePersistedSecret(ws.rootDir, name, "ARC_PAIRING_TOKEN", () => randomBytes(16).toString("base64url"));
|
|
36659
|
+
}
|
|
36660
|
+
cfg = loadDeployConfig(ws.rootDir);
|
|
36661
|
+
}
|
|
36401
36662
|
if (cfg.observability?.enabled) {
|
|
36402
36663
|
const key = cfg.observability.adminPasswordEnv ?? "ARC_OBSERVABILITY_PASSWORD";
|
|
36403
36664
|
ensurePersistedSecret(ws.rootDir, "globals", key, () => crypto.randomUUID().replace(/-/g, ""));
|
|
@@ -36406,14 +36667,14 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
36406
36667
|
err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
|
|
36407
36668
|
process.exit(1);
|
|
36408
36669
|
})() : Object.keys(cfg.envs);
|
|
36409
|
-
const manifestPath =
|
|
36670
|
+
const manifestPath = join21(ws.arcDir, "manifest.json");
|
|
36410
36671
|
if (!options.imageTag) {
|
|
36411
|
-
const needBuild = options.rebuild || !
|
|
36672
|
+
const needBuild = options.rebuild || !existsSync18(manifestPath);
|
|
36412
36673
|
if (needBuild && !options.skipBuild) {
|
|
36413
36674
|
log2("Building platform...");
|
|
36414
36675
|
await buildAll(ws, { noCache: options.rebuild });
|
|
36415
36676
|
ok("Build complete");
|
|
36416
|
-
} else if (!
|
|
36677
|
+
} else if (!existsSync18(manifestPath)) {
|
|
36417
36678
|
err("No build found and --skip-build was set.");
|
|
36418
36679
|
process.exit(1);
|
|
36419
36680
|
}
|
|
@@ -36483,9 +36744,9 @@ function readCliVersion2() {
|
|
|
36483
36744
|
let cur = dirname11(fileURLToPath8(import.meta.url));
|
|
36484
36745
|
const root = dirname11(cur).startsWith("/") ? "/" : ".";
|
|
36485
36746
|
while (cur !== root && cur !== "") {
|
|
36486
|
-
const candidate =
|
|
36487
|
-
if (
|
|
36488
|
-
const pkg = JSON.parse(
|
|
36747
|
+
const candidate = join21(cur, "package.json");
|
|
36748
|
+
if (existsSync18(candidate)) {
|
|
36749
|
+
const pkg = JSON.parse(readFileSync16(candidate, "utf-8"));
|
|
36489
36750
|
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
36490
36751
|
return pkg.version ?? "unknown";
|
|
36491
36752
|
}
|
|
@@ -36501,8 +36762,8 @@ function readCliVersion2() {
|
|
|
36501
36762
|
}
|
|
36502
36763
|
}
|
|
36503
36764
|
async function hashDeployConfig(rootDir) {
|
|
36504
|
-
const p2 =
|
|
36505
|
-
const content =
|
|
36765
|
+
const p2 = join21(rootDir, "deploy.arc.json");
|
|
36766
|
+
const content = readFileSync16(p2);
|
|
36506
36767
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
36507
36768
|
hasher.update(content);
|
|
36508
36769
|
hasher.update(readCliVersion2());
|
|
@@ -36510,8 +36771,9 @@ async function hashDeployConfig(rootDir) {
|
|
|
36510
36771
|
}
|
|
36511
36772
|
|
|
36512
36773
|
// src/platform/startup.ts
|
|
36513
|
-
import {
|
|
36514
|
-
import {
|
|
36774
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
36775
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync16, readFileSync as readFileSync17, watch, writeFileSync as writeFileSync16 } from "fs";
|
|
36776
|
+
import { join as join23 } from "path";
|
|
36515
36777
|
|
|
36516
36778
|
// ../host/src/connection-manager.ts
|
|
36517
36779
|
class ConnectionManager {
|
|
@@ -37947,6 +38209,7 @@ async function createArcServer(config) {
|
|
|
37947
38209
|
const messageChains = new Map;
|
|
37948
38210
|
const coep = config.coep ?? "unsafe-none";
|
|
37949
38211
|
const corsOrigins = config.corsOrigins;
|
|
38212
|
+
const allowPrivateNetwork = config.allowPrivateNetwork ?? false;
|
|
37950
38213
|
const maxRequestBodySize = config.maxRequestBodySize ?? 10 * 1024 * 1024;
|
|
37951
38214
|
const securityHeaders = {
|
|
37952
38215
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -37958,7 +38221,7 @@ async function createArcServer(config) {
|
|
|
37958
38221
|
const origin = req?.headers.get("Origin") || null;
|
|
37959
38222
|
const headers = {
|
|
37960
38223
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
37961
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope, X-Arc-Tokens",
|
|
38224
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope, X-Arc-Tokens, X-Arc-Pairing-Token",
|
|
37962
38225
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
37963
38226
|
"Cross-Origin-Embedder-Policy": coep,
|
|
37964
38227
|
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
@@ -37995,7 +38258,8 @@ async function createArcServer(config) {
|
|
|
37995
38258
|
const url = new URL(req.url);
|
|
37996
38259
|
const corsHeaders2 = buildCorsHeaders(req);
|
|
37997
38260
|
if (req.method === "OPTIONS") {
|
|
37998
|
-
|
|
38261
|
+
const pna = allowPrivateNetwork && req.headers.get("Access-Control-Request-Private-Network") ? { "Access-Control-Allow-Private-Network": "true" } : {};
|
|
38262
|
+
return new Response(null, { headers: { ...corsHeaders2, ...pna } });
|
|
37999
38263
|
}
|
|
38000
38264
|
const handleRequest = async (span) => {
|
|
38001
38265
|
const contentLength = Number(req.headers.get("Content-Length") ?? 0);
|
|
@@ -38137,8 +38401,9 @@ async function createArcServer(config) {
|
|
|
38137
38401
|
}
|
|
38138
38402
|
// src/platform/server.ts
|
|
38139
38403
|
init_i18n();
|
|
38140
|
-
import {
|
|
38141
|
-
import {
|
|
38404
|
+
import { timingSafeEqual } from "crypto";
|
|
38405
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync15 } from "fs";
|
|
38406
|
+
import { join as join22 } from "path";
|
|
38142
38407
|
async function resolveDbAdapterFactory(dbPath) {
|
|
38143
38408
|
const databaseUrl = process.env.DATABASE_URL;
|
|
38144
38409
|
if (databaseUrl && databaseUrl.length > 0) {
|
|
@@ -38148,10 +38413,10 @@ async function resolveDbAdapterFactory(dbPath) {
|
|
|
38148
38413
|
const { createBunSQLiteAdapterFactory: createBunSQLiteAdapterFactory2 } = await Promise.resolve().then(() => (init_dist2(), exports_dist3));
|
|
38149
38414
|
const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
38150
38415
|
if (dbDir)
|
|
38151
|
-
|
|
38416
|
+
mkdirSync15(dbDir, { recursive: true });
|
|
38152
38417
|
return createBunSQLiteAdapterFactory2(dbPath);
|
|
38153
38418
|
}
|
|
38154
|
-
function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
38419
|
+
function generateShellHtml(appName, manifest, initial, stylesHash, shell) {
|
|
38155
38420
|
const otelConfig = process.env.ARC_OTEL_ENABLED === "true" ? {
|
|
38156
38421
|
enabled: true,
|
|
38157
38422
|
endpoint: "/otel",
|
|
@@ -38165,6 +38430,9 @@ function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
|
38165
38430
|
if (!initialUrl) {
|
|
38166
38431
|
throw new Error("generateShellHtml: initial bundle missing from manifest");
|
|
38167
38432
|
}
|
|
38433
|
+
const importMapTag = shell ? `
|
|
38434
|
+
<script type="importmap">${JSON.stringify({ imports: shell.imports })}</script>
|
|
38435
|
+
<script>window.__ARC_PEERS__=${JSON.stringify(shell.peers)};</script>` : "";
|
|
38168
38436
|
const stylesQs = stylesHash ? `?v=${stylesHash.slice(0, 16)}` : "";
|
|
38169
38437
|
return `<!doctype html>
|
|
38170
38438
|
<html lang="en">
|
|
@@ -38175,7 +38443,7 @@ function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
|
38175
38443
|
<link rel="icon" href="${manifest.favicon}">` : ""}${manifest ? `
|
|
38176
38444
|
<link rel="manifest" href="/manifest.json${stylesQs}">` : ""}
|
|
38177
38445
|
<link rel="stylesheet" href="/styles.css${stylesQs}" />
|
|
38178
|
-
<link rel="stylesheet" href="/theme.css${stylesQs}"
|
|
38446
|
+
<link rel="stylesheet" href="/theme.css${stylesQs}" />${importMapTag}
|
|
38179
38447
|
<link rel="modulepreload" href="${initialUrl}" />${otelTag}
|
|
38180
38448
|
</head>
|
|
38181
38449
|
<body>
|
|
@@ -38207,7 +38475,7 @@ function getMime(path4) {
|
|
|
38207
38475
|
return MIME[ext2] ?? "application/octet-stream";
|
|
38208
38476
|
}
|
|
38209
38477
|
function serveFile(filePath, headers = {}) {
|
|
38210
|
-
if (!
|
|
38478
|
+
if (!existsSync19(filePath))
|
|
38211
38479
|
return new Response("Not Found", { status: 404 });
|
|
38212
38480
|
return new Response(Bun.file(filePath), {
|
|
38213
38481
|
headers: { "Content-Type": getMime(filePath), ...headers }
|
|
@@ -38225,11 +38493,44 @@ function ensureModuleSigSecret(ws, devMode) {
|
|
|
38225
38493
|
MODULE_SIG_SECRET = crypto.randomUUID();
|
|
38226
38494
|
}
|
|
38227
38495
|
}
|
|
38228
|
-
function signGroupUrl(file) {
|
|
38496
|
+
function signGroupUrl(file, prefix = "/browser") {
|
|
38229
38497
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
38230
38498
|
hasher.update(`${file}:${MODULE_SIG_SECRET}`);
|
|
38231
38499
|
const sig = hasher.digest("hex").slice(0, 16);
|
|
38232
|
-
return
|
|
38500
|
+
return `${prefix}/${file}?sig=${sig}`;
|
|
38501
|
+
}
|
|
38502
|
+
async function filterGroupsForTokens(groups, moduleAccessMap, tokenPayloads, urlPrefix) {
|
|
38503
|
+
const tokensByName = new Map;
|
|
38504
|
+
for (const t of tokenPayloads) {
|
|
38505
|
+
if (t?.tokenType)
|
|
38506
|
+
tokensByName.set(t.tokenType, t);
|
|
38507
|
+
}
|
|
38508
|
+
const filtered = {};
|
|
38509
|
+
for (const [name, group] of Object.entries(groups)) {
|
|
38510
|
+
let allGranted = true;
|
|
38511
|
+
for (const moduleName of group.modules) {
|
|
38512
|
+
const access = moduleAccessMap.get(moduleName);
|
|
38513
|
+
if (!access || access.rules.length === 0)
|
|
38514
|
+
continue;
|
|
38515
|
+
let granted = false;
|
|
38516
|
+
for (const rule of access.rules) {
|
|
38517
|
+
const matching = tokensByName.get(rule.token.name);
|
|
38518
|
+
if (!matching)
|
|
38519
|
+
continue;
|
|
38520
|
+
granted = rule.check ? await rule.check(matching) : true;
|
|
38521
|
+
if (granted)
|
|
38522
|
+
break;
|
|
38523
|
+
}
|
|
38524
|
+
if (!granted) {
|
|
38525
|
+
allGranted = false;
|
|
38526
|
+
break;
|
|
38527
|
+
}
|
|
38528
|
+
}
|
|
38529
|
+
if (allGranted) {
|
|
38530
|
+
filtered[name] = { ...group, url: signGroupUrl(group.file, urlPrefix) };
|
|
38531
|
+
}
|
|
38532
|
+
}
|
|
38533
|
+
return filtered;
|
|
38233
38534
|
}
|
|
38234
38535
|
function verifyGroupSignature(file, sig) {
|
|
38235
38536
|
if (!sig)
|
|
@@ -38270,36 +38571,7 @@ function parseArcTokensHeader(header) {
|
|
|
38270
38571
|
return payloads;
|
|
38271
38572
|
}
|
|
38272
38573
|
async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
|
|
38273
|
-
const
|
|
38274
|
-
for (const t of tokenPayloads) {
|
|
38275
|
-
if (t?.tokenType)
|
|
38276
|
-
tokensByName.set(t.tokenType, t);
|
|
38277
|
-
}
|
|
38278
|
-
const filteredGroups = {};
|
|
38279
|
-
for (const [name, group] of Object.entries(manifest.groups)) {
|
|
38280
|
-
let allGranted = true;
|
|
38281
|
-
for (const moduleName of group.modules) {
|
|
38282
|
-
const access = moduleAccessMap.get(moduleName);
|
|
38283
|
-
if (!access || access.rules.length === 0)
|
|
38284
|
-
continue;
|
|
38285
|
-
let granted = false;
|
|
38286
|
-
for (const rule of access.rules) {
|
|
38287
|
-
const matching = tokensByName.get(rule.token.name);
|
|
38288
|
-
if (!matching)
|
|
38289
|
-
continue;
|
|
38290
|
-
granted = rule.check ? await rule.check(matching) : true;
|
|
38291
|
-
if (granted)
|
|
38292
|
-
break;
|
|
38293
|
-
}
|
|
38294
|
-
if (!granted) {
|
|
38295
|
-
allGranted = false;
|
|
38296
|
-
break;
|
|
38297
|
-
}
|
|
38298
|
-
}
|
|
38299
|
-
if (allGranted) {
|
|
38300
|
-
filteredGroups[name] = { ...group, url: signGroupUrl(group.file) };
|
|
38301
|
-
}
|
|
38302
|
-
}
|
|
38574
|
+
const filteredGroups = await filterGroupsForTokens(manifest.groups, moduleAccessMap, tokenPayloads, "/browser");
|
|
38303
38575
|
return {
|
|
38304
38576
|
initial: manifest.initial,
|
|
38305
38577
|
groups: filteredGroups,
|
|
@@ -38325,28 +38597,58 @@ function staticFilesHandler(ws, devMode, getManifest) {
|
|
|
38325
38597
|
return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
|
|
38326
38598
|
}
|
|
38327
38599
|
}
|
|
38328
|
-
return serveFile(
|
|
38600
|
+
return serveFile(join22(ws.browserDir, file), {
|
|
38601
|
+
...ctx.corsHeaders,
|
|
38602
|
+
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38603
|
+
});
|
|
38604
|
+
}
|
|
38605
|
+
if (path4.startsWith("/shell/")) {
|
|
38606
|
+
const file = path4.slice(7);
|
|
38607
|
+
if (!BROWSER_FILE_RE.test(file)) {
|
|
38608
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38609
|
+
}
|
|
38610
|
+
return serveFile(join22(ws.arcDir, "shell", file), {
|
|
38611
|
+
...ctx.corsHeaders,
|
|
38612
|
+
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
38613
|
+
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38614
|
+
});
|
|
38615
|
+
}
|
|
38616
|
+
if (path4.startsWith("/browser-fed/")) {
|
|
38617
|
+
const file = path4.slice(13);
|
|
38618
|
+
if (!BROWSER_FILE_RE.test(file)) {
|
|
38619
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38620
|
+
}
|
|
38621
|
+
const fed = getManifest().federation;
|
|
38622
|
+
const isGroupEntry = fed ? Object.values(fed.build.groups).some((g3) => g3.file === file) : false;
|
|
38623
|
+
if (isGroupEntry) {
|
|
38624
|
+
const sig = url.searchParams.get("sig");
|
|
38625
|
+
if (!verifyGroupSignature(file, sig)) {
|
|
38626
|
+
return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
|
|
38627
|
+
}
|
|
38628
|
+
}
|
|
38629
|
+
return serveFile(join22(ws.arcDir, "browser-fed", file), {
|
|
38329
38630
|
...ctx.corsHeaders,
|
|
38631
|
+
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
38330
38632
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38331
38633
|
});
|
|
38332
38634
|
}
|
|
38333
38635
|
if (path4.startsWith("/locales/"))
|
|
38334
|
-
return serveFile(
|
|
38636
|
+
return serveFile(join22(ws.arcDir, path4.slice(1)), {
|
|
38335
38637
|
...ctx.corsHeaders,
|
|
38336
38638
|
"Cache-Control": devMode ? "no-cache" : "max-age=300,stale-while-revalidate=3600"
|
|
38337
38639
|
});
|
|
38338
38640
|
if (path4.startsWith("/assets/"))
|
|
38339
|
-
return serveFile(
|
|
38641
|
+
return serveFile(join22(ws.assetsDir, path4.slice(8)), {
|
|
38340
38642
|
...ctx.corsHeaders,
|
|
38341
38643
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38342
38644
|
});
|
|
38343
38645
|
if (path4 === "/styles.css")
|
|
38344
|
-
return serveFile(
|
|
38646
|
+
return serveFile(join22(ws.arcDir, "styles.css"), {
|
|
38345
38647
|
...ctx.corsHeaders,
|
|
38346
38648
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38347
38649
|
});
|
|
38348
38650
|
if (path4 === "/theme.css")
|
|
38349
|
-
return serveFile(
|
|
38651
|
+
return serveFile(join22(ws.arcDir, "theme.css"), {
|
|
38350
38652
|
...ctx.corsHeaders,
|
|
38351
38653
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38352
38654
|
});
|
|
@@ -38357,15 +38659,114 @@ function staticFilesHandler(ws, devMode, getManifest) {
|
|
|
38357
38659
|
});
|
|
38358
38660
|
}
|
|
38359
38661
|
if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
|
|
38360
|
-
const publicFile =
|
|
38361
|
-
if (
|
|
38662
|
+
const publicFile = join22(ws.publicDir, path4.slice(1));
|
|
38663
|
+
if (existsSync19(publicFile))
|
|
38362
38664
|
return serveFile(publicFile, ctx.corsHeaders);
|
|
38363
38665
|
}
|
|
38364
38666
|
return null;
|
|
38365
38667
|
};
|
|
38366
38668
|
}
|
|
38367
|
-
function
|
|
38669
|
+
function parseCorsOrigins(raw) {
|
|
38670
|
+
if (!raw)
|
|
38671
|
+
return;
|
|
38672
|
+
const list = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
38673
|
+
return list.length > 0 ? list : undefined;
|
|
38674
|
+
}
|
|
38675
|
+
var DISCOVERY_SCHEMA_VERSION = 1;
|
|
38676
|
+
function safeEqual(a2, b4) {
|
|
38677
|
+
const ab = Buffer.from(a2);
|
|
38678
|
+
const bb = Buffer.from(b4);
|
|
38679
|
+
if (ab.length !== bb.length)
|
|
38680
|
+
return false;
|
|
38681
|
+
return timingSafeEqual(ab, bb);
|
|
38682
|
+
}
|
|
38683
|
+
function handleDiscovery(req, ctx, ws, manifest, federation, mapUrl) {
|
|
38684
|
+
if (!federation) {
|
|
38685
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38686
|
+
}
|
|
38687
|
+
const presented = req.headers.get("X-Arc-Pairing-Token");
|
|
38688
|
+
if (!presented || !safeEqual(presented, federation.registrationToken)) {
|
|
38689
|
+
return new Response("Unauthorized", {
|
|
38690
|
+
status: 401,
|
|
38691
|
+
headers: ctx.corsHeaders
|
|
38692
|
+
});
|
|
38693
|
+
}
|
|
38694
|
+
const fed = manifest.federation;
|
|
38695
|
+
const platformUrl = process.env.ARC_PUBLIC_URL || new URL(req.url).origin;
|
|
38696
|
+
return Response.json({
|
|
38697
|
+
meta: {
|
|
38698
|
+
schemaVersion: DISCOVERY_SCHEMA_VERSION,
|
|
38699
|
+
name: ws.appName,
|
|
38700
|
+
version: ws.rootPkg?.version ?? undefined
|
|
38701
|
+
},
|
|
38702
|
+
platformUrl,
|
|
38703
|
+
federation: fed ? {
|
|
38704
|
+
slug: fed.config.slug,
|
|
38705
|
+
auth: fed.config.auth,
|
|
38706
|
+
modules: fed.modules,
|
|
38707
|
+
permissions: fed.permissions,
|
|
38708
|
+
slots: fed.slots
|
|
38709
|
+
} : null,
|
|
38710
|
+
peers: manifest.shell?.peers ?? null,
|
|
38711
|
+
federationManifestUrl: fed ? `${platformUrl}/api/federation/manifest` : null,
|
|
38712
|
+
mapUrl: mapUrl ?? null
|
|
38713
|
+
}, { headers: { ...ctx.corsHeaders, "Cache-Control": "no-cache, private" } });
|
|
38714
|
+
}
|
|
38715
|
+
async function handleFederationRegister(req, ctx, manifest, federation) {
|
|
38716
|
+
const fed = manifest.federation;
|
|
38717
|
+
if (!fed || !federation) {
|
|
38718
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38719
|
+
}
|
|
38720
|
+
const presented = req.headers.get("X-Arc-Pairing-Token");
|
|
38721
|
+
if (!presented || !safeEqual(presented, federation.registrationToken)) {
|
|
38722
|
+
return new Response("Unauthorized", {
|
|
38723
|
+
status: 401,
|
|
38724
|
+
headers: ctx.corsHeaders
|
|
38725
|
+
});
|
|
38726
|
+
}
|
|
38727
|
+
let body;
|
|
38728
|
+
try {
|
|
38729
|
+
body = await req.json();
|
|
38730
|
+
} catch {
|
|
38731
|
+
return new Response("Bad Request", {
|
|
38732
|
+
status: 400,
|
|
38733
|
+
headers: ctx.corsHeaders
|
|
38734
|
+
});
|
|
38735
|
+
}
|
|
38736
|
+
const hostId = typeof body.hostId === "string" ? body.hostId : "";
|
|
38737
|
+
const hostUrl = typeof body.hostUrl === "string" ? body.hostUrl : "";
|
|
38738
|
+
const hostName = typeof body.hostName === "string" ? body.hostName : hostId;
|
|
38739
|
+
const organizationId = typeof body.organizationId === "string" ? body.organizationId : "";
|
|
38740
|
+
if (!hostId || !hostUrl) {
|
|
38741
|
+
return new Response("Bad Request: hostId + hostUrl required", {
|
|
38742
|
+
status: 400,
|
|
38743
|
+
headers: ctx.corsHeaders
|
|
38744
|
+
});
|
|
38745
|
+
}
|
|
38746
|
+
ok(`Federation: host "${hostName}" (${hostUrl}) registered for org ${organizationId || "\u2014"}`);
|
|
38747
|
+
return Response.json({
|
|
38748
|
+
secret: federation.appSecret,
|
|
38749
|
+
slug: fed.config.slug,
|
|
38750
|
+
auth: fed.config.auth,
|
|
38751
|
+
modules: fed.modules,
|
|
38752
|
+
permissions: fed.permissions,
|
|
38753
|
+
slots: fed.slots
|
|
38754
|
+
}, { headers: { ...ctx.corsHeaders, "Cache-Control": "no-store" } });
|
|
38755
|
+
}
|
|
38756
|
+
function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap, telemetry, federation, getMapUrl) {
|
|
38368
38757
|
return (req, url, ctx) => {
|
|
38758
|
+
if (url.pathname === "/api/discovery") {
|
|
38759
|
+
return handleDiscovery(req, ctx, ws, getManifest(), federation, getMapUrl?.());
|
|
38760
|
+
}
|
|
38761
|
+
if (url.pathname === "/api/federation/register") {
|
|
38762
|
+
if (req.method !== "POST") {
|
|
38763
|
+
return new Response("Method Not Allowed", {
|
|
38764
|
+
status: 405,
|
|
38765
|
+
headers: { ...ctx.corsHeaders, Allow: "POST, OPTIONS" }
|
|
38766
|
+
});
|
|
38767
|
+
}
|
|
38768
|
+
return handleFederationRegister(req, ctx, getManifest(), federation);
|
|
38769
|
+
}
|
|
38369
38770
|
if (url.pathname === "/api/modules") {
|
|
38370
38771
|
const arcTokensHeader = req.headers.get("X-Arc-Tokens");
|
|
38371
38772
|
let payloads = parseArcTokensHeader(arcTokensHeader);
|
|
@@ -38379,6 +38780,36 @@ function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap, telemetry) {
|
|
|
38379
38780
|
}
|
|
38380
38781
|
}));
|
|
38381
38782
|
}
|
|
38783
|
+
if (url.pathname === "/api/federation/manifest") {
|
|
38784
|
+
const m4 = getManifest();
|
|
38785
|
+
if (!m4.federation || !m4.shell) {
|
|
38786
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38787
|
+
}
|
|
38788
|
+
const arcTokensHeader = req.headers.get("X-Arc-Tokens");
|
|
38789
|
+
let payloads = parseArcTokensHeader(arcTokensHeader);
|
|
38790
|
+
if (payloads.length === 0 && ctx.tokenPayload) {
|
|
38791
|
+
payloads = [ctx.tokenPayload];
|
|
38792
|
+
}
|
|
38793
|
+
const fed = m4.federation;
|
|
38794
|
+
return filterGroupsForTokens(fed.build.groups, moduleAccessMap, payloads, "/browser-fed").then((groups) => Response.json({
|
|
38795
|
+
initial: fed.build.initial,
|
|
38796
|
+
groups,
|
|
38797
|
+
sharedChunks: fed.build.sharedChunks,
|
|
38798
|
+
stylesHash: m4.stylesHash,
|
|
38799
|
+
peers: m4.shell.peers,
|
|
38800
|
+
app: {
|
|
38801
|
+
slug: fed.config.slug,
|
|
38802
|
+
name: ws.appName,
|
|
38803
|
+
version: ws.rootPkg.version ?? undefined,
|
|
38804
|
+
modules: fed.modules,
|
|
38805
|
+
permissions: fed.permissions,
|
|
38806
|
+
slots: fed.slots,
|
|
38807
|
+
auth: fed.config.auth
|
|
38808
|
+
}
|
|
38809
|
+
}, {
|
|
38810
|
+
headers: { ...ctx.corsHeaders, "Cache-Control": "no-cache, private" }
|
|
38811
|
+
}));
|
|
38812
|
+
}
|
|
38382
38813
|
if (url.pathname === "/api/translations") {
|
|
38383
38814
|
const config = readTranslationsConfig(ws.rootDir);
|
|
38384
38815
|
return Response.json(config ?? { locales: [], sourceLocale: "" }, {
|
|
@@ -38424,6 +38855,17 @@ function devReloadHandler(sseClients) {
|
|
|
38424
38855
|
});
|
|
38425
38856
|
};
|
|
38426
38857
|
}
|
|
38858
|
+
function headlessFallbackHandler(ws, getManifest) {
|
|
38859
|
+
return (_req, _url, ctx) => {
|
|
38860
|
+
const m4 = getManifest();
|
|
38861
|
+
return Response.json({
|
|
38862
|
+
arc: "headless",
|
|
38863
|
+
app: ws.appName,
|
|
38864
|
+
federation: m4.federation ?? null,
|
|
38865
|
+
endpoints: ["/api/federation/manifest", "/api/modules", "/health"]
|
|
38866
|
+
}, { headers: ctx.corsHeaders });
|
|
38867
|
+
};
|
|
38868
|
+
}
|
|
38427
38869
|
function spaFallbackHandler(getShellHtml) {
|
|
38428
38870
|
return (_req, _url, ctx) => {
|
|
38429
38871
|
return new Response(getShellHtml(), {
|
|
@@ -38466,8 +38908,14 @@ async function startPlatformServer(opts) {
|
|
|
38466
38908
|
const setManifest = (m4) => {
|
|
38467
38909
|
manifest = m4;
|
|
38468
38910
|
};
|
|
38469
|
-
|
|
38911
|
+
let mapUrl = opts.mapUrl;
|
|
38912
|
+
const setMapUrl = (u2) => {
|
|
38913
|
+
mapUrl = u2;
|
|
38914
|
+
};
|
|
38915
|
+
const getMapUrl = () => mapUrl;
|
|
38916
|
+
const getShellHtml = () => generateShellHtml(ws.appName, ws.manifest, manifest?.initial, manifest?.stylesHash, manifest?.shell);
|
|
38470
38917
|
const sseClients = new Set;
|
|
38918
|
+
const fallbackHandler = opts.headless ? headlessFallbackHandler(ws, getManifest) : spaFallbackHandler(getShellHtml);
|
|
38471
38919
|
const notifyReload = (m4) => {
|
|
38472
38920
|
const data = JSON.stringify(m4);
|
|
38473
38921
|
for (const c2 of sseClients) {
|
|
@@ -38493,18 +38941,23 @@ async function startPlatformServer(opts) {
|
|
|
38493
38941
|
port,
|
|
38494
38942
|
async fetch(req) {
|
|
38495
38943
|
const url = new URL(req.url);
|
|
38496
|
-
if (req.method === "OPTIONS")
|
|
38497
|
-
|
|
38944
|
+
if (req.method === "OPTIONS") {
|
|
38945
|
+
const headers = { ...cors };
|
|
38946
|
+
if (req.headers.get("Access-Control-Request-Private-Network")) {
|
|
38947
|
+
headers["Access-Control-Allow-Private-Network"] = "true";
|
|
38948
|
+
}
|
|
38949
|
+
return new Response(null, { headers });
|
|
38950
|
+
}
|
|
38498
38951
|
const ctx = {
|
|
38499
38952
|
rawToken: null,
|
|
38500
38953
|
tokenPayload: null,
|
|
38501
38954
|
corsHeaders: cors
|
|
38502
38955
|
};
|
|
38503
38956
|
const handlers = [
|
|
38504
|
-
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
|
|
38957
|
+
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry, opts.federation, getMapUrl),
|
|
38505
38958
|
devReloadHandler(sseClients),
|
|
38506
38959
|
staticFilesHandler(ws, !!devMode, getManifest),
|
|
38507
|
-
|
|
38960
|
+
fallbackHandler
|
|
38508
38961
|
];
|
|
38509
38962
|
for (const handler of handlers) {
|
|
38510
38963
|
const response = await handler(req, url, ctx);
|
|
@@ -38519,11 +38972,12 @@ async function startPlatformServer(opts) {
|
|
|
38519
38972
|
contextHandler: null,
|
|
38520
38973
|
connectionManager: null,
|
|
38521
38974
|
setManifest,
|
|
38975
|
+
setMapUrl,
|
|
38522
38976
|
notifyReload,
|
|
38523
38977
|
stop: () => server.stop()
|
|
38524
38978
|
};
|
|
38525
38979
|
}
|
|
38526
|
-
const dbPath = opts.dbPath ||
|
|
38980
|
+
const dbPath = opts.dbPath || join22(ws.arcDir, "data", "arc.db");
|
|
38527
38981
|
const baseDbFactory = await resolveDbAdapterFactory(dbPath);
|
|
38528
38982
|
let dbAdapterFactory = baseDbFactory;
|
|
38529
38983
|
if (telemetry) {
|
|
@@ -38539,11 +38993,13 @@ async function startPlatformServer(opts) {
|
|
|
38539
38993
|
dbAdapterFactory,
|
|
38540
38994
|
port,
|
|
38541
38995
|
coep,
|
|
38996
|
+
corsOrigins: parseCorsOrigins(process.env.ARC_CORS_ORIGINS),
|
|
38997
|
+
allowPrivateNetwork: !!devMode,
|
|
38542
38998
|
httpHandlers: [
|
|
38543
|
-
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
|
|
38999
|
+
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry, opts.federation, getMapUrl),
|
|
38544
39000
|
devReloadHandler(sseClients),
|
|
38545
39001
|
staticFilesHandler(ws, !!devMode, getManifest),
|
|
38546
|
-
|
|
39002
|
+
fallbackHandler
|
|
38547
39003
|
],
|
|
38548
39004
|
onWsClose: (clientId) => cleanupClientSubs(clientId),
|
|
38549
39005
|
telemetry
|
|
@@ -38553,6 +39009,7 @@ async function startPlatformServer(opts) {
|
|
|
38553
39009
|
contextHandler: arcServer.contextHandler,
|
|
38554
39010
|
connectionManager: arcServer.connectionManager,
|
|
38555
39011
|
setManifest,
|
|
39012
|
+
setMapUrl,
|
|
38556
39013
|
notifyReload,
|
|
38557
39014
|
stop: () => {
|
|
38558
39015
|
arcServer.stop();
|
|
@@ -38565,17 +39022,23 @@ async function startPlatformServer(opts) {
|
|
|
38565
39022
|
async function startPlatform(opts) {
|
|
38566
39023
|
const { ws, devMode } = opts;
|
|
38567
39024
|
const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
|
|
38568
|
-
const dbPath = opts.dbPath ??
|
|
39025
|
+
const dbPath = opts.dbPath ?? join23(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
|
|
38569
39026
|
let manifest;
|
|
38570
39027
|
if (devMode) {
|
|
38571
39028
|
manifest = await buildAll(ws);
|
|
38572
39029
|
} else {
|
|
38573
|
-
const manifestPath =
|
|
38574
|
-
if (!
|
|
39030
|
+
const manifestPath = join23(ws.arcDir, "manifest.json");
|
|
39031
|
+
if (!existsSync20(manifestPath)) {
|
|
38575
39032
|
err("No build found. Run `arc platform build` first.");
|
|
38576
39033
|
process.exit(1);
|
|
38577
39034
|
}
|
|
38578
|
-
manifest = JSON.parse(
|
|
39035
|
+
manifest = JSON.parse(readFileSync17(manifestPath, "utf-8"));
|
|
39036
|
+
}
|
|
39037
|
+
assertProdFederationSecrets(devMode, !!manifest.federation);
|
|
39038
|
+
const needsPairing = !!manifest.federation || devMode && (opts.map || opts.headless);
|
|
39039
|
+
const pairingToken = needsPairing ? resolvePairingToken(ws.rootDir) : undefined;
|
|
39040
|
+
if (manifest.federation) {
|
|
39041
|
+
process.env.ARC_APP_SECRET = resolveAppSecret(ws.rootDir);
|
|
38579
39042
|
}
|
|
38580
39043
|
log2("Loading server context...");
|
|
38581
39044
|
const { context: context2, moduleAccess } = await loadServerContext(ws);
|
|
@@ -38596,7 +39059,12 @@ async function startPlatform(opts) {
|
|
|
38596
39059
|
context: context2,
|
|
38597
39060
|
moduleAccess,
|
|
38598
39061
|
dbPath,
|
|
38599
|
-
devMode
|
|
39062
|
+
devMode,
|
|
39063
|
+
headless: opts.headless,
|
|
39064
|
+
federation: manifest.federation && pairingToken ? {
|
|
39065
|
+
registrationToken: pairingToken,
|
|
39066
|
+
appSecret: process.env.ARC_APP_SECRET
|
|
39067
|
+
} : undefined
|
|
38600
39068
|
});
|
|
38601
39069
|
break;
|
|
38602
39070
|
} catch (e2) {
|
|
@@ -38617,8 +39085,18 @@ async function startPlatform(opts) {
|
|
|
38617
39085
|
ok("Commands, queries, WebSocket \u2014 all on same port");
|
|
38618
39086
|
}
|
|
38619
39087
|
let onReload;
|
|
38620
|
-
if (devMode && opts.map) {
|
|
38621
|
-
onReload = await startMapTool(ws, actualPort + 1
|
|
39088
|
+
if (devMode && (opts.map || opts.headless)) {
|
|
39089
|
+
onReload = await startMapTool(ws, actualPort + 1, pairingToken, platform3, {
|
|
39090
|
+
platformUrl: `http://localhost:${actualPort}`,
|
|
39091
|
+
federation: manifest.federation ? {
|
|
39092
|
+
slug: manifest.federation.config.slug,
|
|
39093
|
+
auth: manifest.federation.config.auth,
|
|
39094
|
+
modules: manifest.federation.modules,
|
|
39095
|
+
permissions: manifest.federation.permissions,
|
|
39096
|
+
slots: manifest.federation.slots
|
|
39097
|
+
} : undefined,
|
|
39098
|
+
peers: manifest.shell?.peers
|
|
39099
|
+
});
|
|
38622
39100
|
}
|
|
38623
39101
|
if (devMode) {
|
|
38624
39102
|
attachDevWatcher(ws, platform3, onReload);
|
|
@@ -38630,25 +39108,98 @@ async function startPlatform(opts) {
|
|
|
38630
39108
|
process.on("SIGTERM", cleanup);
|
|
38631
39109
|
process.on("SIGINT", cleanup);
|
|
38632
39110
|
}
|
|
38633
|
-
async function startMapTool(ws, port) {
|
|
39111
|
+
async function startMapTool(ws, port, authToken, platformServer, platform3) {
|
|
38634
39112
|
try {
|
|
38635
|
-
const { startMapServer } = await import("@arcote.tech/arc-map");
|
|
39113
|
+
const { startMapServer, MAP_SCHEMA_VERSION } = await import("@arcote.tech/arc-map");
|
|
38636
39114
|
const { getContext } = await import("@arcote.tech/platform");
|
|
38637
|
-
const globs = ws.packages.map((p3) =>
|
|
38638
|
-
const packageOf = (absFile) => ws.packages.find((p3) => absFile.startsWith(
|
|
39115
|
+
const globs = ws.packages.map((p3) => join23(p3.path, "src", "**", "*.{ts,tsx}"));
|
|
39116
|
+
const packageOf = (absFile) => ws.packages.find((p3) => absFile.startsWith(join23(p3.path, "src")))?.name;
|
|
39117
|
+
const meta = readAppMeta(ws.rootDir, MAP_SCHEMA_VERSION);
|
|
38639
39118
|
const map = await startMapServer({
|
|
38640
39119
|
getContext: () => getContext(),
|
|
38641
39120
|
scan: { globs, workspaceRoot: ws.rootDir, packageOf },
|
|
38642
39121
|
useCasePackages: ws.packages.map((p3) => ({ name: p3.name, path: p3.path })),
|
|
38643
|
-
port
|
|
39122
|
+
port,
|
|
39123
|
+
authToken,
|
|
39124
|
+
meta,
|
|
39125
|
+
platform: platform3
|
|
38644
39126
|
});
|
|
39127
|
+
platformServer.setMapUrl(map.url);
|
|
38645
39128
|
ok(`Context map \u2192 ${map.url}`);
|
|
39129
|
+
ok(`Pairing token: ${authToken}`);
|
|
38646
39130
|
return map.notifyReload;
|
|
38647
39131
|
} catch (e2) {
|
|
38648
39132
|
err(`Context map failed to start: ${e2.message}`);
|
|
38649
39133
|
return;
|
|
38650
39134
|
}
|
|
38651
39135
|
}
|
|
39136
|
+
function assertProdFederationSecrets(devMode, hasFederation) {
|
|
39137
|
+
if (devMode || !hasFederation)
|
|
39138
|
+
return;
|
|
39139
|
+
const missing = [];
|
|
39140
|
+
if (!process.env.ARC_APP_SECRET)
|
|
39141
|
+
missing.push("ARC_APP_SECRET");
|
|
39142
|
+
if (!process.env.ARC_PAIRING_TOKEN && !process.env.ARC_MAP_TOKEN) {
|
|
39143
|
+
missing.push("ARC_PAIRING_TOKEN");
|
|
39144
|
+
}
|
|
39145
|
+
if (missing.length === 0)
|
|
39146
|
+
return;
|
|
39147
|
+
err(`Federacja w produkcji wymaga zmiennych \u015Brodowiskowych: ${missing.join(", ")}.
|
|
39148
|
+
` + ` Bez nich sekret i token s\u0105 generowane w kontenerze i GIN\u0104 przy pierwszym
|
|
39149
|
+
` + ` recreate \u2014 hosty musia\u0142yby przeinstalowa\u0107 aplikacj\u0119, bez \u017Cadnego b\u0142\u0119du.
|
|
39150
|
+
` + ` Ustaw je w deployu (deploy.arc.json \u2192 envVars + deploy.arc.<env>.env).`);
|
|
39151
|
+
process.exit(1);
|
|
39152
|
+
}
|
|
39153
|
+
function resolveAppSecret(rootDir) {
|
|
39154
|
+
const fromEnv = process.env.ARC_APP_SECRET;
|
|
39155
|
+
if (fromEnv)
|
|
39156
|
+
return fromEnv;
|
|
39157
|
+
const file = join23(rootDir, ".arc", "app-secret");
|
|
39158
|
+
try {
|
|
39159
|
+
const saved = readFileSync17(file, "utf-8").trim();
|
|
39160
|
+
if (saved)
|
|
39161
|
+
return saved;
|
|
39162
|
+
} catch {}
|
|
39163
|
+
const secret = randomBytes2(32).toString("base64url");
|
|
39164
|
+
try {
|
|
39165
|
+
mkdirSync16(join23(rootDir, ".arc"), { recursive: true });
|
|
39166
|
+
writeFileSync16(file, secret, { mode: 384 });
|
|
39167
|
+
} catch (e2) {
|
|
39168
|
+
err(`App secret not persisted (${e2.message}) \u2014 hosts will need re-install after restart`);
|
|
39169
|
+
}
|
|
39170
|
+
return secret;
|
|
39171
|
+
}
|
|
39172
|
+
function resolvePairingToken(rootDir) {
|
|
39173
|
+
const fromEnv = process.env.ARC_PAIRING_TOKEN || process.env.ARC_MAP_TOKEN;
|
|
39174
|
+
if (fromEnv)
|
|
39175
|
+
return fromEnv;
|
|
39176
|
+
const file = join23(rootDir, ".arc", "pairing-token");
|
|
39177
|
+
try {
|
|
39178
|
+
const saved = readFileSync17(file, "utf-8").trim();
|
|
39179
|
+
if (saved)
|
|
39180
|
+
return saved;
|
|
39181
|
+
} catch {}
|
|
39182
|
+
const token = randomBytes2(16).toString("base64url");
|
|
39183
|
+
try {
|
|
39184
|
+
mkdirSync16(join23(rootDir, ".arc"), { recursive: true });
|
|
39185
|
+
writeFileSync16(file, token, { mode: 384 });
|
|
39186
|
+
} catch (e2) {
|
|
39187
|
+
err(`Pairing token not persisted (${e2.message}) \u2014 it will change on restart`);
|
|
39188
|
+
}
|
|
39189
|
+
return token;
|
|
39190
|
+
}
|
|
39191
|
+
function readAppMeta(rootDir, schemaVersion) {
|
|
39192
|
+
try {
|
|
39193
|
+
const pkg = JSON.parse(readFileSync17(join23(rootDir, "package.json"), "utf-8"));
|
|
39194
|
+
return {
|
|
39195
|
+
schemaVersion,
|
|
39196
|
+
name: pkg.name ?? rootDir.split("/").pop() ?? "arc-app",
|
|
39197
|
+
version: pkg.version
|
|
39198
|
+
};
|
|
39199
|
+
} catch {
|
|
39200
|
+
return { schemaVersion, name: rootDir.split("/").pop() ?? "arc-app" };
|
|
39201
|
+
}
|
|
39202
|
+
}
|
|
38652
39203
|
function attachDevWatcher(ws, platform3, onReload) {
|
|
38653
39204
|
log2("Watching for changes...");
|
|
38654
39205
|
let rebuildTimer = null;
|
|
@@ -38675,8 +39226,8 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38675
39226
|
}, 300);
|
|
38676
39227
|
};
|
|
38677
39228
|
for (const pkg of ws.packages) {
|
|
38678
|
-
const srcDir =
|
|
38679
|
-
if (!
|
|
39229
|
+
const srcDir = join23(pkg.path, "src");
|
|
39230
|
+
if (!existsSync20(srcDir))
|
|
38680
39231
|
continue;
|
|
38681
39232
|
watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
38682
39233
|
if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
|
|
@@ -38686,8 +39237,8 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38686
39237
|
triggerRebuild();
|
|
38687
39238
|
});
|
|
38688
39239
|
}
|
|
38689
|
-
const localesDir =
|
|
38690
|
-
if (
|
|
39240
|
+
const localesDir = join23(ws.rootDir, "locales");
|
|
39241
|
+
if (existsSync20(localesDir)) {
|
|
38691
39242
|
watch(localesDir, { recursive: false }, (_event, filename) => {
|
|
38692
39243
|
if (!filename?.endsWith(".po"))
|
|
38693
39244
|
return;
|
|
@@ -38699,7 +39250,12 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38699
39250
|
// src/commands/platform-dev.ts
|
|
38700
39251
|
async function platformDev(opts = {}) {
|
|
38701
39252
|
const ws = resolveWorkspace();
|
|
38702
|
-
await startPlatform({
|
|
39253
|
+
await startPlatform({
|
|
39254
|
+
ws,
|
|
39255
|
+
devMode: true,
|
|
39256
|
+
map: opts.map,
|
|
39257
|
+
headless: opts.headless
|
|
39258
|
+
});
|
|
38703
39259
|
}
|
|
38704
39260
|
|
|
38705
39261
|
// src/commands/platform-start.ts
|
|
@@ -38714,7 +39270,11 @@ program2.name("arc").description("CLI tool for Arc framework").version("0.0.3");
|
|
|
38714
39270
|
program2.command("dev").description("Run development mode for Arc framework").action(dev);
|
|
38715
39271
|
program2.command("build").description("Build all clients and declarations").action(build);
|
|
38716
39272
|
var platform3 = program2.command("platform").description("Platform commands \u2014 run full stack (server + UI)");
|
|
38717
|
-
platform3.command("dev").description("Start platform in dev mode (Bun server + Vite HMR)").option("--no-cache", "Force full rebuild on startup").option("--map", "Also start the Arc Context Map dev tool on a separate port").
|
|
39273
|
+
platform3.command("dev").description("Start platform in dev mode (Bun server + Vite HMR)").option("--no-cache", "Force full rebuild on startup").option("--map", "Also start the Arc Context Map dev tool on a separate port").option("--headless", "Host API + federated module chunks + map, WITHOUT the app's own SPA shell (implies --map)").action((opts) => platformDev({
|
|
39274
|
+
noCache: opts.cache === false,
|
|
39275
|
+
map: opts.map,
|
|
39276
|
+
headless: opts.headless
|
|
39277
|
+
}));
|
|
38718
39278
|
platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
|
|
38719
39279
|
platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
|
|
38720
39280
|
platform3.command("deploy [env]").description("Deploy platform to a remote server (reads deploy.arc.json, surveys if missing)").option("--skip-build", "Skip local build step").option("--rebuild", "Force rebuild before deploy").option("--build-only", "Build the Docker image locally, then exit (no remote push)").option("--image-tag <hash>", "Roll back / pin to an existing image tag instead of building a new one").option("--force-bootstrap", "Re-run Ansible host bootstrap even if the server is already configured").action((env2, opts) => platformDeploy(env2, opts));
|