@arcote.tech/arc-cli 0.7.31 → 0.8.0
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 +915 -370
- 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 +34 -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 +438 -45
- package/src/platform/shared.ts +119 -8
- package/src/platform/startup.ts +208 -4
package/dist/index.js
CHANGED
|
@@ -23070,7 +23070,7 @@ var {
|
|
|
23070
23070
|
// ../../node_modules/.bun/find-up@7.0.0/node_modules/find-up/index.js
|
|
23071
23071
|
import path2 from "path";
|
|
23072
23072
|
|
|
23073
|
-
// ../../node_modules/.bun/
|
|
23073
|
+
// ../../node_modules/.bun/find-up@7.0.0/node_modules/find-up/node_modules/locate-path/index.js
|
|
23074
23074
|
import process2 from "process";
|
|
23075
23075
|
import path from "path";
|
|
23076
23076
|
import fs, { promises as fsPromises } from "fs";
|
|
@@ -31298,20 +31298,20 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
|
|
|
31298
31298
|
}
|
|
31299
31299
|
|
|
31300
31300
|
// src/platform/shared.ts
|
|
31301
|
-
import { copyFileSync, existsSync as
|
|
31302
|
-
import { dirname as dirname8, join as
|
|
31301
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
31302
|
+
import { dirname as dirname8, join as join13 } from "path";
|
|
31303
31303
|
|
|
31304
31304
|
// src/builder/module-builder.ts
|
|
31305
31305
|
import { execSync } from "child_process";
|
|
31306
31306
|
import {
|
|
31307
|
-
existsSync as
|
|
31308
|
-
mkdirSync as
|
|
31309
|
-
readFileSync as
|
|
31307
|
+
existsSync as existsSync8,
|
|
31308
|
+
mkdirSync as mkdirSync7,
|
|
31309
|
+
readFileSync as readFileSync8,
|
|
31310
31310
|
readdirSync as readdirSync4,
|
|
31311
|
-
rmSync as
|
|
31312
|
-
writeFileSync as
|
|
31311
|
+
rmSync as rmSync3,
|
|
31312
|
+
writeFileSync as writeFileSync7
|
|
31313
31313
|
} from "fs";
|
|
31314
|
-
import { basename as
|
|
31314
|
+
import { basename as basename3, dirname as dirname6, join as join9, relative as relative3 } from "path";
|
|
31315
31315
|
import { builtinModules } from "module";
|
|
31316
31316
|
init_i18n();
|
|
31317
31317
|
init_compile();
|
|
@@ -31390,10 +31390,21 @@ var FRAMEWORK_PEERS = [...ARC_PEERS, ...REACT_PEERS];
|
|
|
31390
31390
|
var SHELL_EXTERNALS = [
|
|
31391
31391
|
...FRAMEWORK_PEERS,
|
|
31392
31392
|
"react/jsx-runtime",
|
|
31393
|
-
"react/
|
|
31393
|
+
"react-dom/client"
|
|
31394
31394
|
];
|
|
31395
31395
|
var FRAMEWORK_PEER_SET = new Set(FRAMEWORK_PEERS);
|
|
31396
31396
|
var SHELL_EXTERNAL_SET = new Set(SHELL_EXTERNALS);
|
|
31397
|
+
function shortNameOf(pkg) {
|
|
31398
|
+
if (pkg === "@arcote.tech/platform")
|
|
31399
|
+
return "platform";
|
|
31400
|
+
if (pkg.startsWith("@arcote.tech/"))
|
|
31401
|
+
return pkg.slice("@arcote.tech/".length);
|
|
31402
|
+
return pkg;
|
|
31403
|
+
}
|
|
31404
|
+
|
|
31405
|
+
// src/builder/peer-shell.ts
|
|
31406
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
31407
|
+
import { basename as basename2, join as join8 } from "path";
|
|
31397
31408
|
|
|
31398
31409
|
// src/builder/hash.ts
|
|
31399
31410
|
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync } from "fs";
|
|
@@ -31474,6 +31485,88 @@ function mtimeOf(path4) {
|
|
|
31474
31485
|
}
|
|
31475
31486
|
}
|
|
31476
31487
|
|
|
31488
|
+
// src/builder/peer-shell.ts
|
|
31489
|
+
var RUNTIME_GLOBAL = "__ARC_RT";
|
|
31490
|
+
function peerVersion(rootDir, pkg) {
|
|
31491
|
+
try {
|
|
31492
|
+
const p = JSON.parse(readFileSync7(join8(rootDir, "node_modules", pkg, "package.json"), "utf-8"));
|
|
31493
|
+
return p.version ?? "0.0.0";
|
|
31494
|
+
} catch {
|
|
31495
|
+
return "0.0.0";
|
|
31496
|
+
}
|
|
31497
|
+
}
|
|
31498
|
+
async function namedExportsOf(specifier) {
|
|
31499
|
+
try {
|
|
31500
|
+
const mod = await import(specifier);
|
|
31501
|
+
return Object.keys(mod).filter((k) => k !== "default" && /^[A-Za-z_$][\w$]*$/.test(k));
|
|
31502
|
+
} catch {
|
|
31503
|
+
return [];
|
|
31504
|
+
}
|
|
31505
|
+
}
|
|
31506
|
+
function runtimeExposePrelude() {
|
|
31507
|
+
const lines = SHELL_EXTERNALS.map((spec, i) => `import * as __rt${i} from ${JSON.stringify(spec)};`);
|
|
31508
|
+
const assigns = SHELL_EXTERNALS.map((spec, i) => ` ${JSON.stringify(spec)}: (__rt${i}.default ?? __rt${i}),
|
|
31509
|
+
${JSON.stringify(spec + "#ns")}: __rt${i},`);
|
|
31510
|
+
return lines.join(`
|
|
31511
|
+
`) + `
|
|
31512
|
+
globalThis.${RUNTIME_GLOBAL} = Object.assign(globalThis.${RUNTIME_GLOBAL} ?? {}, {
|
|
31513
|
+
` + assigns.join(`
|
|
31514
|
+
`) + `
|
|
31515
|
+
});
|
|
31516
|
+
`;
|
|
31517
|
+
}
|
|
31518
|
+
async function buildPeerShell(rootDir, outDir, cache, noCache) {
|
|
31519
|
+
mkdirSync6(outDir, { recursive: true });
|
|
31520
|
+
const peers = {};
|
|
31521
|
+
for (const pkg of FRAMEWORK_PEERS)
|
|
31522
|
+
peers[pkg] = peerVersion(rootDir, pkg);
|
|
31523
|
+
const namedBySpec = {};
|
|
31524
|
+
for (const spec of SHELL_EXTERNALS)
|
|
31525
|
+
namedBySpec[spec] = await namedExportsOf(spec);
|
|
31526
|
+
const unitId = "peer-shell";
|
|
31527
|
+
const inputHash = sha256OfJson({
|
|
31528
|
+
peers,
|
|
31529
|
+
externals: SHELL_EXTERNALS,
|
|
31530
|
+
named: namedBySpec,
|
|
31531
|
+
mode: "globalThis",
|
|
31532
|
+
rev: 5
|
|
31533
|
+
});
|
|
31534
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash)) {
|
|
31535
|
+
const cached = cache.units[unitId]?.outputHashes;
|
|
31536
|
+
if (cached?._manifest) {
|
|
31537
|
+
try {
|
|
31538
|
+
const m = JSON.parse(cached._manifest);
|
|
31539
|
+
const files = Object.values(m.imports).map((p) => basename2(p));
|
|
31540
|
+
if (files.every((f) => existsSync7(join8(outDir, f)))) {
|
|
31541
|
+
console.log(` \u2713 cached: ${unitId}`);
|
|
31542
|
+
return { ...m, cached: true };
|
|
31543
|
+
}
|
|
31544
|
+
} catch {}
|
|
31545
|
+
}
|
|
31546
|
+
}
|
|
31547
|
+
console.log(` building: ${unitId} (${SHELL_EXTERNALS.length} peers, globalThis)`);
|
|
31548
|
+
const imports = {};
|
|
31549
|
+
for (const specifier of SHELL_EXTERNALS) {
|
|
31550
|
+
const short = shortNameOf(specifier).replace(/\//g, "-");
|
|
31551
|
+
const named = namedBySpec[specifier] ?? [];
|
|
31552
|
+
const src = `const M = globalThis.${RUNTIME_GLOBAL}[${JSON.stringify(specifier)}];
|
|
31553
|
+
const N = globalThis.${RUNTIME_GLOBAL}[${JSON.stringify(specifier + "#ns")}] ?? M;
|
|
31554
|
+
export default M;
|
|
31555
|
+
` + named.map((n) => `export const ${n} = N[${JSON.stringify(n)}];`).join(`
|
|
31556
|
+
`) + `
|
|
31557
|
+
`;
|
|
31558
|
+
const hash = sha256Hex(src).slice(0, 16);
|
|
31559
|
+
const finalName = `${short}.${hash}.js`;
|
|
31560
|
+
writeFileSync6(join8(outDir, finalName), src);
|
|
31561
|
+
imports[specifier] = `/shell/${finalName}`;
|
|
31562
|
+
}
|
|
31563
|
+
const manifest = { imports, peers, cached: false };
|
|
31564
|
+
updateCache(cache, unitId, inputHash, {
|
|
31565
|
+
outputHashes: { _manifest: JSON.stringify(manifest) }
|
|
31566
|
+
});
|
|
31567
|
+
return manifest;
|
|
31568
|
+
}
|
|
31569
|
+
|
|
31477
31570
|
// src/builder/parallel.ts
|
|
31478
31571
|
import { cpus } from "os";
|
|
31479
31572
|
var DEFAULT_CONCURRENCY = Math.max(1, cpus().length - 1);
|
|
@@ -31527,10 +31620,10 @@ function isBuiltinSpec(spec) {
|
|
|
31527
31620
|
function versionFromImporter(importer, name) {
|
|
31528
31621
|
let dir = importer ? dirname6(importer) : "";
|
|
31529
31622
|
while (dir && dir !== dirname6(dir)) {
|
|
31530
|
-
const pj =
|
|
31531
|
-
if (
|
|
31623
|
+
const pj = join9(dir, "package.json");
|
|
31624
|
+
if (existsSync8(pj)) {
|
|
31532
31625
|
try {
|
|
31533
|
-
const json = JSON.parse(
|
|
31626
|
+
const json = JSON.parse(readFileSync8(pj, "utf-8"));
|
|
31534
31627
|
const spec = json.dependencies?.[name] ?? json.peerDependencies?.[name] ?? json.devDependencies?.[name];
|
|
31535
31628
|
if (typeof spec === "string")
|
|
31536
31629
|
return spec;
|
|
@@ -31584,11 +31677,7 @@ function jsxDevShimPlugin() {
|
|
|
31584
31677
|
namespace: "jsx-dev-shim"
|
|
31585
31678
|
}));
|
|
31586
31679
|
build2.onLoad({ filter: /.*/, namespace: "jsx-dev-shim" }, () => ({
|
|
31587
|
-
contents: `
|
|
31588
|
-
export const jsxDEV = jsx;
|
|
31589
|
-
export const jsxsDEV = jsxs;
|
|
31590
|
-
export { Fragment };
|
|
31591
|
-
`,
|
|
31680
|
+
contents: `export { jsx as jsxDEV, jsxs as jsxsDEV, Fragment } from "react/jsx-runtime";`,
|
|
31592
31681
|
loader: "ts"
|
|
31593
31682
|
}));
|
|
31594
31683
|
}
|
|
@@ -31601,13 +31690,13 @@ var SERVER_DEFINES = { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT:
|
|
|
31601
31690
|
var SERVER_ENTRY_FILE = "_server.js";
|
|
31602
31691
|
var SERVER_EXTERNALS_FILE = "_externals.json";
|
|
31603
31692
|
function discoverPackages(rootDir) {
|
|
31604
|
-
const rootPkg = JSON.parse(
|
|
31693
|
+
const rootPkg = JSON.parse(readFileSync8(join9(rootDir, "package.json"), "utf-8"));
|
|
31605
31694
|
const workspaceGlobs = rootPkg.workspaces ?? [];
|
|
31606
31695
|
const results = [];
|
|
31607
31696
|
for (const glob2 of workspaceGlobs) {
|
|
31608
31697
|
const base2 = glob2.replace("/*", "");
|
|
31609
|
-
const baseDir =
|
|
31610
|
-
if (!
|
|
31698
|
+
const baseDir = join9(rootDir, base2);
|
|
31699
|
+
if (!existsSync8(baseDir))
|
|
31611
31700
|
continue;
|
|
31612
31701
|
let entries;
|
|
31613
31702
|
try {
|
|
@@ -31616,25 +31705,25 @@ function discoverPackages(rootDir) {
|
|
|
31616
31705
|
continue;
|
|
31617
31706
|
}
|
|
31618
31707
|
for (const entry of entries) {
|
|
31619
|
-
const pkgPath =
|
|
31620
|
-
if (!
|
|
31708
|
+
const pkgPath = join9(baseDir, entry, "package.json");
|
|
31709
|
+
if (!existsSync8(pkgPath))
|
|
31621
31710
|
continue;
|
|
31622
|
-
const pkg = JSON.parse(
|
|
31711
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
31623
31712
|
if (pkg.name?.startsWith("@arcote.tech/"))
|
|
31624
31713
|
continue;
|
|
31625
|
-
const pkgDir =
|
|
31714
|
+
const pkgDir = join9(baseDir, entry);
|
|
31626
31715
|
const candidates = [
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31716
|
+
join9(pkgDir, "src", "index.ts"),
|
|
31717
|
+
join9(pkgDir, "src", "index.tsx"),
|
|
31718
|
+
join9(pkgDir, "index.ts"),
|
|
31719
|
+
join9(pkgDir, "index.tsx")
|
|
31631
31720
|
];
|
|
31632
|
-
const entrypoint = candidates.find((c) =>
|
|
31721
|
+
const entrypoint = candidates.find((c) => existsSync8(c)) ?? null;
|
|
31633
31722
|
if (!entrypoint)
|
|
31634
31723
|
continue;
|
|
31635
31724
|
results.push({
|
|
31636
31725
|
name: pkg.name,
|
|
31637
|
-
path:
|
|
31726
|
+
path: join9(baseDir, entry),
|
|
31638
31727
|
entrypoint,
|
|
31639
31728
|
packageJson: pkg
|
|
31640
31729
|
});
|
|
@@ -31663,7 +31752,7 @@ var sourceFilter = (rel) => {
|
|
|
31663
31752
|
return true;
|
|
31664
31753
|
};
|
|
31665
31754
|
function pkgSourceHash(pkg) {
|
|
31666
|
-
return sha256OfDir(
|
|
31755
|
+
return sha256OfDir(join9(pkg.path, "src"), sourceFilter);
|
|
31667
31756
|
}
|
|
31668
31757
|
function depVersionsHash(rootDir, pkg) {
|
|
31669
31758
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
@@ -31676,7 +31765,7 @@ function depVersionsHash(rootDir, pkg) {
|
|
|
31676
31765
|
}
|
|
31677
31766
|
async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
31678
31767
|
const unitId = `context-pkg:${pkg.name}:${client.name}`;
|
|
31679
|
-
const outDir =
|
|
31768
|
+
const outDir = join9(pkg.path, "dist", client.name);
|
|
31680
31769
|
const inputHash = sha256OfJson({
|
|
31681
31770
|
src: pkgSourceHash(pkg),
|
|
31682
31771
|
pkg: pkg.packageJson,
|
|
@@ -31685,7 +31774,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
31685
31774
|
target: client.target,
|
|
31686
31775
|
defines: client.defines
|
|
31687
31776
|
});
|
|
31688
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
31777
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join9(outDir, "main", "index.js")])) {
|
|
31689
31778
|
console.log(` \u2713 cached: ${pkg.name} (${client.name})`);
|
|
31690
31779
|
return { pkgName: pkg.name, client: client.name, declarationErrors: [], cached: true };
|
|
31691
31780
|
}
|
|
@@ -31695,7 +31784,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
31695
31784
|
const externals = [...peerDeps, ...Object.keys(allDeps)];
|
|
31696
31785
|
const result = await Bun.build({
|
|
31697
31786
|
entrypoints: [pkg.entrypoint],
|
|
31698
|
-
outdir:
|
|
31787
|
+
outdir: join9(outDir, "main"),
|
|
31699
31788
|
target: client.target,
|
|
31700
31789
|
format: "esm",
|
|
31701
31790
|
naming: "index.[ext]",
|
|
@@ -31765,7 +31854,7 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
|
|
|
31765
31854
|
}
|
|
31766
31855
|
async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
31767
31856
|
const contexts = packages.filter((p) => isContextPackage(p.packageJson));
|
|
31768
|
-
|
|
31857
|
+
mkdirSync7(serverDir, { recursive: true });
|
|
31769
31858
|
const srcByName = new Map(packages.map((p) => [p.name, p.entrypoint]));
|
|
31770
31859
|
const externalSet = new Set(FRAMEWORK_PEERS);
|
|
31771
31860
|
for (const p of packages) {
|
|
@@ -31786,8 +31875,8 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31786
31875
|
defines: SERVER_DEFINES,
|
|
31787
31876
|
sourcemap: "linked"
|
|
31788
31877
|
});
|
|
31789
|
-
const entryFileAbs =
|
|
31790
|
-
const externalsFileAbs =
|
|
31878
|
+
const entryFileAbs = join9(serverDir, SERVER_ENTRY_FILE);
|
|
31879
|
+
const externalsFileAbs = join9(serverDir, SERVER_EXTERNALS_FILE);
|
|
31791
31880
|
if (!noCache && isCacheHit(cache, unitId, inputHash, [entryFileAbs])) {
|
|
31792
31881
|
console.log(` \u2713 cached: ${unitId}`);
|
|
31793
31882
|
return {
|
|
@@ -31799,13 +31888,13 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31799
31888
|
console.log(` building: ${unitId} (${contexts.length} server modules)`);
|
|
31800
31889
|
for (const f of readdirSync4(serverDir)) {
|
|
31801
31890
|
if (f.endsWith(".js") || f.endsWith(".js.map")) {
|
|
31802
|
-
|
|
31891
|
+
rmSync3(join9(serverDir, f), { force: true });
|
|
31803
31892
|
}
|
|
31804
31893
|
}
|
|
31805
|
-
const tmpDir =
|
|
31806
|
-
|
|
31807
|
-
const entrySrc =
|
|
31808
|
-
|
|
31894
|
+
const tmpDir = join9(serverDir, "_entries");
|
|
31895
|
+
mkdirSync7(tmpDir, { recursive: true });
|
|
31896
|
+
const entrySrc = join9(tmpDir, SERVER_ENTRY_FILE.replace(/\.js$/, ".ts"));
|
|
31897
|
+
writeFileSync7(entrySrc, contexts.map((p) => `import "${p.name}";`).join(`
|
|
31809
31898
|
`) + `
|
|
31810
31899
|
`);
|
|
31811
31900
|
const recorded = new Map;
|
|
@@ -31828,7 +31917,7 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31828
31917
|
define: SERVER_DEFINES
|
|
31829
31918
|
});
|
|
31830
31919
|
} finally {
|
|
31831
|
-
|
|
31920
|
+
rmSync3(tmpDir, { recursive: true, force: true });
|
|
31832
31921
|
}
|
|
31833
31922
|
if (!result.success) {
|
|
31834
31923
|
for (const log2 of result.logs)
|
|
@@ -31839,31 +31928,33 @@ async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
|
31839
31928
|
if (!entryOut) {
|
|
31840
31929
|
throw new Error("Server app build: entry not found in outputs");
|
|
31841
31930
|
}
|
|
31842
|
-
if (
|
|
31843
|
-
throw new Error(`Server app build: unexpected entry name ${
|
|
31931
|
+
if (basename3(entryOut.path) !== SERVER_ENTRY_FILE) {
|
|
31932
|
+
throw new Error(`Server app build: unexpected entry name ${basename3(entryOut.path)} (wanted ${SERVER_ENTRY_FILE})`);
|
|
31844
31933
|
}
|
|
31845
31934
|
const externals = [...recorded].filter(([name]) => !FRAMEWORK_PEER_SET.has(name)).map(([name, version]) => ({ name, version })).sort((a, b) => a.name.localeCompare(b.name));
|
|
31846
|
-
|
|
31935
|
+
writeFileSync7(externalsFileAbs, JSON.stringify(externals, null, 2) + `
|
|
31847
31936
|
`);
|
|
31848
31937
|
const outputHash = sha256OfDir(serverDir);
|
|
31849
31938
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
31850
31939
|
return { entryFile: SERVER_ENTRY_FILE, cached: false, externals };
|
|
31851
31940
|
}
|
|
31852
31941
|
function readServerExternals(path4) {
|
|
31853
|
-
if (!
|
|
31942
|
+
if (!existsSync8(path4))
|
|
31854
31943
|
return [];
|
|
31855
31944
|
try {
|
|
31856
|
-
const parsed = JSON.parse(
|
|
31945
|
+
const parsed = JSON.parse(readFileSync8(path4, "utf-8"));
|
|
31857
31946
|
return Array.isArray(parsed) ? parsed : [];
|
|
31858
31947
|
} catch {
|
|
31859
31948
|
return [];
|
|
31860
31949
|
}
|
|
31861
31950
|
}
|
|
31862
|
-
async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector) {
|
|
31863
|
-
|
|
31951
|
+
async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector, opts) {
|
|
31952
|
+
const federated = opts?.federated ?? false;
|
|
31953
|
+
const exposeRuntime = opts?.exposeRuntime ?? false;
|
|
31954
|
+
mkdirSync7(outDir, { recursive: true });
|
|
31864
31955
|
const publicMembers = plan.groups.get("public") ?? [];
|
|
31865
31956
|
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";
|
|
31957
|
+
const unitId = opts?.unitId ?? "browser-app";
|
|
31867
31958
|
const allMembers = [];
|
|
31868
31959
|
for (const m of publicMembers) {
|
|
31869
31960
|
allMembers.push({ name: m.pkg.name, group: "public", srcHash: pkgSourceHash(m.pkg) });
|
|
@@ -31879,7 +31970,11 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
|
|
|
31879
31970
|
"initial",
|
|
31880
31971
|
...protectedGroups.map((g) => g.name).sort()
|
|
31881
31972
|
],
|
|
31882
|
-
define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" }
|
|
31973
|
+
define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" },
|
|
31974
|
+
federated,
|
|
31975
|
+
exposeRuntime,
|
|
31976
|
+
externals: federated ? [...SHELL_EXTERNALS] : [],
|
|
31977
|
+
builderRev: 3
|
|
31883
31978
|
});
|
|
31884
31979
|
if (!noCache && isCacheHit(cache, unitId, inputHash)) {
|
|
31885
31980
|
const cached = cache.units[unitId]?.outputHashes;
|
|
@@ -31891,7 +31986,7 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
|
|
|
31891
31986
|
...Object.values(m.groups).map((g) => g.file),
|
|
31892
31987
|
...m.sharedChunks
|
|
31893
31988
|
];
|
|
31894
|
-
if (allFiles.every((f) =>
|
|
31989
|
+
if (allFiles.every((f) => existsSync8(join9(outDir, f)))) {
|
|
31895
31990
|
console.log(` \u2713 cached: ${unitId}`);
|
|
31896
31991
|
return { ...m, cached: true };
|
|
31897
31992
|
}
|
|
@@ -31899,25 +31994,26 @@ async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollec
|
|
|
31899
31994
|
}
|
|
31900
31995
|
}
|
|
31901
31996
|
console.log(` building: ${unitId} (initial: ${publicMembers.length} modules, groups: ${protectedGroups.map((g) => `${g.name}=${g.members.length}`).join(",") || "none"})`);
|
|
31902
|
-
if (
|
|
31997
|
+
if (existsSync8(outDir)) {
|
|
31903
31998
|
for (const f of readdirSync4(outDir)) {
|
|
31904
31999
|
if (f.endsWith(".js"))
|
|
31905
|
-
|
|
32000
|
+
rmSync3(join9(outDir, f), { force: true });
|
|
31906
32001
|
}
|
|
31907
32002
|
}
|
|
31908
|
-
const tmpDir =
|
|
31909
|
-
|
|
32003
|
+
const tmpDir = join9(outDir, "_entries");
|
|
32004
|
+
mkdirSync7(tmpDir, { recursive: true });
|
|
31910
32005
|
const importLines = (pkgs) => pkgs.map((m) => `import "${m.pkg.name}";`).join(`
|
|
31911
32006
|
`);
|
|
31912
|
-
const initialEntry =
|
|
31913
|
-
|
|
32007
|
+
const initialEntry = join9(tmpDir, "initial.ts");
|
|
32008
|
+
writeFileSync7(initialEntry, (exposeRuntime ? runtimeExposePrelude() + `
|
|
32009
|
+
` : "") + `${importLines(publicMembers)}
|
|
31914
32010
|
export { startApp } from "@arcote.tech/platform";
|
|
31915
32011
|
`);
|
|
31916
32012
|
const entryPaths = [initialEntry];
|
|
31917
32013
|
const groupModuleMap = new Map;
|
|
31918
32014
|
for (const g of protectedGroups) {
|
|
31919
|
-
const entry =
|
|
31920
|
-
|
|
32015
|
+
const entry = join9(tmpDir, `${g.name}.ts`);
|
|
32016
|
+
writeFileSync7(entry, `${importLines(g.members)}
|
|
31921
32017
|
`);
|
|
31922
32018
|
entryPaths.push(entry);
|
|
31923
32019
|
groupModuleMap.set(g.name, g.members.map((m) => m.moduleName));
|
|
@@ -31930,15 +32026,15 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31930
32026
|
allMemberPkgs.set(m.pkg.name, m.pkg);
|
|
31931
32027
|
const patchedPkgJsons = [];
|
|
31932
32028
|
for (const pkg of allMemberPkgs.values()) {
|
|
31933
|
-
const pkgJsonPath =
|
|
31934
|
-
if (!
|
|
32029
|
+
const pkgJsonPath = join9(pkg.path, "package.json");
|
|
32030
|
+
if (!existsSync8(pkgJsonPath))
|
|
31935
32031
|
continue;
|
|
31936
|
-
const original =
|
|
32032
|
+
const original = readFileSync8(pkgJsonPath, "utf-8");
|
|
31937
32033
|
const parsed = JSON.parse(original);
|
|
31938
32034
|
if (parsed.sideEffects === true)
|
|
31939
32035
|
continue;
|
|
31940
32036
|
parsed.sideEffects = true;
|
|
31941
|
-
|
|
32037
|
+
writeFileSync7(pkgJsonPath, JSON.stringify(parsed, null, 2) + `
|
|
31942
32038
|
`);
|
|
31943
32039
|
patchedPkgJsons.push({ path: pkgJsonPath, original });
|
|
31944
32040
|
}
|
|
@@ -31950,9 +32046,9 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31950
32046
|
splitting: true,
|
|
31951
32047
|
format: "esm",
|
|
31952
32048
|
target: "browser",
|
|
31953
|
-
external: [],
|
|
32049
|
+
external: federated ? [...SHELL_EXTERNALS] : [],
|
|
31954
32050
|
plugins: [
|
|
31955
|
-
singleReactPlugin(rootDir),
|
|
32051
|
+
...federated ? [] : [singleReactPlugin(rootDir)],
|
|
31956
32052
|
jsxDevShimPlugin(),
|
|
31957
32053
|
i18nExtractPlugin(i18nCollector, rootDir)
|
|
31958
32054
|
],
|
|
@@ -31966,9 +32062,9 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31966
32062
|
});
|
|
31967
32063
|
} finally {
|
|
31968
32064
|
for (const p of patchedPkgJsons)
|
|
31969
|
-
|
|
32065
|
+
writeFileSync7(p.path, p.original);
|
|
31970
32066
|
}
|
|
31971
|
-
|
|
32067
|
+
rmSync3(tmpDir, { recursive: true, force: true });
|
|
31972
32068
|
if (!result.success) {
|
|
31973
32069
|
for (const log2 of result.logs)
|
|
31974
32070
|
console.error(log2);
|
|
@@ -31979,16 +32075,16 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
31979
32075
|
const groups = {};
|
|
31980
32076
|
const sharedChunks = [];
|
|
31981
32077
|
for (const out of result.outputs) {
|
|
31982
|
-
const name =
|
|
32078
|
+
const name = basename3(out.path);
|
|
31983
32079
|
if (out.kind === "entry-point") {
|
|
31984
|
-
const bytes =
|
|
32080
|
+
const bytes = readFileSync8(out.path);
|
|
31985
32081
|
const hash = sha256Hex(bytes).slice(0, 16);
|
|
31986
32082
|
const stem = name.replace(/\.js$/, "");
|
|
31987
32083
|
const finalName = `${stem}.${hash}.js`;
|
|
31988
|
-
const finalPath =
|
|
31989
|
-
|
|
31990
|
-
|
|
31991
|
-
|
|
32084
|
+
const finalPath = join9(outDir, finalName);
|
|
32085
|
+
rmSync3(finalPath, { force: true });
|
|
32086
|
+
writeFileSync7(finalPath, bytes);
|
|
32087
|
+
rmSync3(out.path, { force: true });
|
|
31992
32088
|
if (stem === "initial") {
|
|
31993
32089
|
initialFile = finalName;
|
|
31994
32090
|
initialHash = hash;
|
|
@@ -32018,21 +32114,21 @@ export { startApp } from "@arcote.tech/platform";
|
|
|
32018
32114
|
return manifest;
|
|
32019
32115
|
}
|
|
32020
32116
|
async function buildTranslations(rootDir, arcDir, cache, noCache) {
|
|
32021
|
-
const localesDir =
|
|
32022
|
-
if (!
|
|
32117
|
+
const localesDir = join9(rootDir, "locales");
|
|
32118
|
+
if (!existsSync8(localesDir))
|
|
32023
32119
|
return;
|
|
32024
32120
|
const unitId = "translations";
|
|
32025
|
-
const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) =>
|
|
32121
|
+
const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) => join9(localesDir, f));
|
|
32026
32122
|
if (poFiles.length === 0)
|
|
32027
32123
|
return;
|
|
32028
32124
|
const inputHash = sha256OfFiles(poFiles);
|
|
32029
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
32125
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join9(arcDir, "locales")])) {
|
|
32030
32126
|
console.log(` \u2713 cached: translations`);
|
|
32031
32127
|
return;
|
|
32032
32128
|
}
|
|
32033
32129
|
console.log(` building: translations (${poFiles.length} catalog(s))`);
|
|
32034
|
-
compileAllCatalogs(localesDir,
|
|
32035
|
-
const jsonFiles = readdirSync4(
|
|
32130
|
+
compileAllCatalogs(localesDir, join9(arcDir, "locales"));
|
|
32131
|
+
const jsonFiles = readdirSync4(join9(arcDir, "locales")).filter((f) => f.endsWith(".json")).map((f) => join9(arcDir, "locales", f));
|
|
32036
32132
|
const outputHash = sha256OfFiles(jsonFiles);
|
|
32037
32133
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
32038
32134
|
}
|
|
@@ -32099,10 +32195,10 @@ var TAILWIND_INPUT_TEMPLATE = (rootRel) => `@import "tailwindcss";
|
|
|
32099
32195
|
}
|
|
32100
32196
|
`;
|
|
32101
32197
|
async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache) {
|
|
32102
|
-
|
|
32103
|
-
const inputCss =
|
|
32104
|
-
const outputCss =
|
|
32105
|
-
const themeOutput =
|
|
32198
|
+
mkdirSync7(arcDir, { recursive: true });
|
|
32199
|
+
const inputCss = join9(arcDir, "_input.css");
|
|
32200
|
+
const outputCss = join9(arcDir, "styles.css");
|
|
32201
|
+
const themeOutput = join9(arcDir, "theme.css");
|
|
32106
32202
|
const rootRel = relative3(arcDir, rootDir).replace(/\\/g, "/");
|
|
32107
32203
|
const inputCssContent = TAILWIND_INPUT_TEMPLATE(rootRel);
|
|
32108
32204
|
const tsxFilter = (rel) => {
|
|
@@ -32114,15 +32210,15 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
32114
32210
|
};
|
|
32115
32211
|
const wsHashes = {};
|
|
32116
32212
|
for (const p of packages) {
|
|
32117
|
-
wsHashes[p.name] = sha256OfDir(
|
|
32213
|
+
wsHashes[p.name] = sha256OfDir(join9(p.path, "src"), tsxFilter);
|
|
32118
32214
|
}
|
|
32119
|
-
const platformSrc =
|
|
32120
|
-
const arcDsSrc =
|
|
32215
|
+
const platformSrc = join9(rootDir, "node_modules", "@arcote.tech", "platform", "src");
|
|
32216
|
+
const arcDsSrc = join9(rootDir, "node_modules", "@arcote.tech", "arc-ds", "src");
|
|
32121
32217
|
const frameworkHashes = {
|
|
32122
32218
|
platform: sha256OfDir(platformSrc, tsxFilter),
|
|
32123
32219
|
arcDs: sha256OfDir(arcDsSrc, tsxFilter)
|
|
32124
32220
|
};
|
|
32125
|
-
const themeContent = themePath &&
|
|
32221
|
+
const themeContent = themePath && existsSync8(join9(rootDir, themePath)) ? readFileSync8(join9(rootDir, themePath)) : null;
|
|
32126
32222
|
const themeHash = themeContent ? sha256Hex(themeContent) : null;
|
|
32127
32223
|
const unitId = "styles";
|
|
32128
32224
|
const inputHash = sha256OfJson({
|
|
@@ -32139,16 +32235,16 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
32139
32235
|
return;
|
|
32140
32236
|
}
|
|
32141
32237
|
console.log(` building: styles`);
|
|
32142
|
-
|
|
32238
|
+
writeFileSync7(inputCss, inputCssContent);
|
|
32143
32239
|
execSync(`bunx @tailwindcss/cli -i ${inputCss} -o ${outputCss} --minify`, {
|
|
32144
32240
|
cwd: rootDir,
|
|
32145
32241
|
stdio: "inherit"
|
|
32146
32242
|
});
|
|
32147
32243
|
if (themePath && themeContent) {
|
|
32148
|
-
|
|
32244
|
+
writeFileSync7(themeOutput, themeContent);
|
|
32149
32245
|
}
|
|
32150
32246
|
const outFiles = [outputCss];
|
|
32151
|
-
if (themePath &&
|
|
32247
|
+
if (themePath && existsSync8(themeOutput))
|
|
32152
32248
|
outFiles.push(themeOutput);
|
|
32153
32249
|
const outputHash = sha256OfFiles(outFiles);
|
|
32154
32250
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
@@ -32157,20 +32253,20 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
32157
32253
|
// src/builder/access-extractor.ts
|
|
32158
32254
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
32159
32255
|
import {
|
|
32160
|
-
existsSync as
|
|
32161
|
-
mkdirSync as
|
|
32162
|
-
readFileSync as
|
|
32256
|
+
existsSync as existsSync9,
|
|
32257
|
+
mkdirSync as mkdirSync8,
|
|
32258
|
+
readFileSync as readFileSync9,
|
|
32163
32259
|
unlinkSync as unlinkSync2,
|
|
32164
|
-
writeFileSync as
|
|
32260
|
+
writeFileSync as writeFileSync8
|
|
32165
32261
|
} from "fs";
|
|
32166
|
-
import { join as
|
|
32262
|
+
import { join as join10 } from "path";
|
|
32167
32263
|
async function extractAccessMap(rootDir, serverBundlePath) {
|
|
32168
|
-
const serverBundles =
|
|
32169
|
-
const workerDir =
|
|
32170
|
-
|
|
32171
|
-
const workerPath =
|
|
32172
|
-
const outPath =
|
|
32173
|
-
|
|
32264
|
+
const serverBundles = existsSync9(serverBundlePath) ? [{ name: "server", path: serverBundlePath }] : [];
|
|
32265
|
+
const workerDir = join10(rootDir, ".arc", ".tmp");
|
|
32266
|
+
mkdirSync8(workerDir, { recursive: true });
|
|
32267
|
+
const workerPath = join10(workerDir, `access-extractor-${Date.now()}.mjs`);
|
|
32268
|
+
const outPath = join10(workerDir, `access-${Date.now()}.json`);
|
|
32269
|
+
writeFileSync8(workerPath, WORKER_SOURCE);
|
|
32174
32270
|
try {
|
|
32175
32271
|
const proc2 = spawn2({
|
|
32176
32272
|
cmd: ["bun", "run", workerPath],
|
|
@@ -32187,7 +32283,7 @@ async function extractAccessMap(rootDir, serverBundlePath) {
|
|
|
32187
32283
|
if (exit !== 0) {
|
|
32188
32284
|
throw new Error(`access-extractor subprocess exited with ${exit}`);
|
|
32189
32285
|
}
|
|
32190
|
-
return JSON.parse(
|
|
32286
|
+
return JSON.parse(readFileSync9(outPath, "utf-8"));
|
|
32191
32287
|
} finally {
|
|
32192
32288
|
try {
|
|
32193
32289
|
unlinkSync2(workerPath);
|
|
@@ -32224,23 +32320,35 @@ for (const { name, path } of bundles) {
|
|
|
32224
32320
|
}
|
|
32225
32321
|
}
|
|
32226
32322
|
|
|
32227
|
-
const
|
|
32228
|
-
for (const [name,
|
|
32229
|
-
|
|
32230
|
-
rules: (
|
|
32323
|
+
const access = {};
|
|
32324
|
+
for (const [name, a] of platform.getAllModuleAccess()) {
|
|
32325
|
+
access[name] = {
|
|
32326
|
+
rules: (a.rules ?? []).map((r) => ({
|
|
32231
32327
|
token: { name: r.token?.name ?? "" },
|
|
32232
32328
|
hasCheck: typeof r.check === "function",
|
|
32233
32329
|
})),
|
|
32234
32330
|
};
|
|
32235
32331
|
}
|
|
32236
32332
|
|
|
32333
|
+
const exposure = {};
|
|
32334
|
+
const getExposure = platform.getAllModuleExposure;
|
|
32335
|
+
if (typeof getExposure === "function") {
|
|
32336
|
+
for (const [name, e] of getExposure()) {
|
|
32337
|
+
exposure[name] = {
|
|
32338
|
+
exposure: e.exposure,
|
|
32339
|
+
permissions: e.permissions ?? [],
|
|
32340
|
+
slots: e.slots ?? [],
|
|
32341
|
+
};
|
|
32342
|
+
}
|
|
32343
|
+
}
|
|
32344
|
+
|
|
32237
32345
|
const { writeFileSync } = await import("node:fs");
|
|
32238
|
-
writeFileSync(out, JSON.stringify(
|
|
32346
|
+
writeFileSync(out, JSON.stringify({ access, exposure }, null, 2) + "\\n");
|
|
32239
32347
|
`.trim();
|
|
32240
32348
|
|
|
32241
32349
|
// src/builder/chunk-planner.ts
|
|
32242
|
-
import { readFileSync as
|
|
32243
|
-
import { basename as
|
|
32350
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
32351
|
+
import { basename as basename4, join as join11 } from "path";
|
|
32244
32352
|
var PUBLIC_CHUNK = "public";
|
|
32245
32353
|
function planChunks(packages, accessMap) {
|
|
32246
32354
|
const assignments = new Map;
|
|
@@ -32253,7 +32361,7 @@ function planChunks(packages, accessMap) {
|
|
|
32253
32361
|
pkg,
|
|
32254
32362
|
chunk,
|
|
32255
32363
|
moduleName,
|
|
32256
|
-
safeName:
|
|
32364
|
+
safeName: basename4(pkg.path)
|
|
32257
32365
|
};
|
|
32258
32366
|
assignments.set(moduleName, entry);
|
|
32259
32367
|
const bucket = groups.get(chunk);
|
|
@@ -32283,7 +32391,7 @@ function collectModuleNames(dir, acc) {
|
|
|
32283
32391
|
for (const e of entries) {
|
|
32284
32392
|
if (e === "node_modules" || e === "dist")
|
|
32285
32393
|
continue;
|
|
32286
|
-
const full =
|
|
32394
|
+
const full = join11(dir, e);
|
|
32287
32395
|
let isDir = false;
|
|
32288
32396
|
try {
|
|
32289
32397
|
isDir = statSync2(full).isDirectory();
|
|
@@ -32293,7 +32401,7 @@ function collectModuleNames(dir, acc) {
|
|
|
32293
32401
|
if (isDir) {
|
|
32294
32402
|
collectModuleNames(full, acc);
|
|
32295
32403
|
} else if (e.endsWith(".ts") || e.endsWith(".tsx")) {
|
|
32296
|
-
const src =
|
|
32404
|
+
const src = readFileSync10(full, "utf-8");
|
|
32297
32405
|
MODULE_CALL.lastIndex = 0;
|
|
32298
32406
|
let m;
|
|
32299
32407
|
while (m = MODULE_CALL.exec(src))
|
|
@@ -32305,7 +32413,7 @@ function assertOneModulePerPackage(packages) {
|
|
|
32305
32413
|
const offenders = [];
|
|
32306
32414
|
for (const pkg of packages) {
|
|
32307
32415
|
const names = new Set;
|
|
32308
|
-
collectModuleNames(
|
|
32416
|
+
collectModuleNames(join11(pkg.path, "src"), names);
|
|
32309
32417
|
if (names.size > 1) {
|
|
32310
32418
|
offenders.push({ pkg: pkg.name, modules: [...names].sort() });
|
|
32311
32419
|
}
|
|
@@ -32337,11 +32445,11 @@ function resolveChunk(moduleName, access) {
|
|
|
32337
32445
|
|
|
32338
32446
|
// src/builder/dependency-collector.ts
|
|
32339
32447
|
import { createHash } from "crypto";
|
|
32340
|
-
import { existsSync as
|
|
32341
|
-
import { basename as
|
|
32448
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
32449
|
+
import { basename as basename5, dirname as dirname7, join as join12 } from "path";
|
|
32342
32450
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
32343
32451
|
function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], serverExternals = []) {
|
|
32344
|
-
|
|
32452
|
+
mkdirSync9(arcDir, { recursive: true });
|
|
32345
32453
|
const versions = resolveFrameworkVersions(rootDir, packages);
|
|
32346
32454
|
mergeServerExternals(versions, rootDir, packages, serverExternals);
|
|
32347
32455
|
for (const { name, version } of sharedDeps) {
|
|
@@ -32350,7 +32458,7 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], server
|
|
|
32350
32458
|
try {
|
|
32351
32459
|
const cliDir = dirname7(fileURLToPath6(import.meta.url));
|
|
32352
32460
|
const arcOtelPkgPath = Bun.resolveSync("@arcote.tech/arc-otel/package.json", cliDir);
|
|
32353
|
-
const arcOtelPkg = JSON.parse(
|
|
32461
|
+
const arcOtelPkg = JSON.parse(readFileSync11(arcOtelPkgPath, "utf-8"));
|
|
32354
32462
|
for (const [name, spec] of Object.entries(arcOtelPkg.dependencies ?? {})) {
|
|
32355
32463
|
if (name.startsWith("@opentelemetry/")) {
|
|
32356
32464
|
versions[name] = spec;
|
|
@@ -32361,10 +32469,10 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], server
|
|
|
32361
32469
|
console.warn(`[arc-otel] could not resolve @arcote.tech/arc-otel \u2014 image will run without telemetry deps: ${e.message}`);
|
|
32362
32470
|
}
|
|
32363
32471
|
let rootArc;
|
|
32364
|
-
const rootPkgPath =
|
|
32365
|
-
if (
|
|
32472
|
+
const rootPkgPath = join12(rootDir, "package.json");
|
|
32473
|
+
if (existsSync10(rootPkgPath)) {
|
|
32366
32474
|
try {
|
|
32367
|
-
const rootPkg = JSON.parse(
|
|
32475
|
+
const rootPkg = JSON.parse(readFileSync11(rootPkgPath, "utf-8"));
|
|
32368
32476
|
if (rootPkg.arc && typeof rootPkg.arc === "object")
|
|
32369
32477
|
rootArc = rootPkg.arc;
|
|
32370
32478
|
} catch {}
|
|
@@ -32376,11 +32484,11 @@ function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = [], server
|
|
|
32376
32484
|
dependencies: versions,
|
|
32377
32485
|
...rootArc ? { arc: rootArc } : {}
|
|
32378
32486
|
};
|
|
32379
|
-
const manifestPath =
|
|
32380
|
-
|
|
32487
|
+
const manifestPath = join12(arcDir, "package.json");
|
|
32488
|
+
writeFileSync9(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
32381
32489
|
`);
|
|
32382
|
-
const hash = sha256Hex2(
|
|
32383
|
-
|
|
32490
|
+
const hash = sha256Hex2(readFileSync11(manifestPath));
|
|
32491
|
+
writeFileSync9(join12(arcDir, ".deps-hash"), hash + `
|
|
32384
32492
|
`);
|
|
32385
32493
|
return { hash, manifestPath };
|
|
32386
32494
|
}
|
|
@@ -32388,7 +32496,7 @@ function sha256Hex2(bytes) {
|
|
|
32388
32496
|
return createHash("sha256").update(bytes).digest("hex");
|
|
32389
32497
|
}
|
|
32390
32498
|
function resolveFrameworkVersions(rootDir, packages) {
|
|
32391
|
-
const rootPkg = JSON.parse(
|
|
32499
|
+
const rootPkg = JSON.parse(readFileSync11(join12(rootDir, "package.json"), "utf-8"));
|
|
32392
32500
|
const rootDeps = rootPkg.dependencies ?? {};
|
|
32393
32501
|
const rootDevDeps = rootPkg.devDependencies ?? {};
|
|
32394
32502
|
const required = new Set(FRAMEWORK_PEERS);
|
|
@@ -32419,7 +32527,7 @@ function findWorkspaceSpec(packages, name) {
|
|
|
32419
32527
|
return;
|
|
32420
32528
|
}
|
|
32421
32529
|
function mergeServerExternals(versions, rootDir, packages, externals) {
|
|
32422
|
-
const rootPkg = JSON.parse(
|
|
32530
|
+
const rootPkg = JSON.parse(readFileSync11(join12(rootDir, "package.json"), "utf-8"));
|
|
32423
32531
|
const rootDeps = rootPkg.dependencies ?? {};
|
|
32424
32532
|
const rootDevDeps = rootPkg.devDependencies ?? {};
|
|
32425
32533
|
for (const { name, version } of externals) {
|
|
@@ -32447,9 +32555,9 @@ function resolveWorkspace() {
|
|
|
32447
32555
|
process.exit(1);
|
|
32448
32556
|
}
|
|
32449
32557
|
const rootDir = dirname8(packageJsonPath);
|
|
32450
|
-
const rootPkg = JSON.parse(
|
|
32558
|
+
const rootPkg = JSON.parse(readFileSync12(packageJsonPath, "utf-8"));
|
|
32451
32559
|
const appName = rootPkg.name ?? "Arc App";
|
|
32452
|
-
const arcDir =
|
|
32560
|
+
const arcDir = join13(rootDir, ".arc", "platform");
|
|
32453
32561
|
log2("Scanning workspaces...");
|
|
32454
32562
|
const packages = discoverPackages(rootDir);
|
|
32455
32563
|
if (packages.length > 0) {
|
|
@@ -32459,10 +32567,10 @@ function resolveWorkspace() {
|
|
|
32459
32567
|
}
|
|
32460
32568
|
let manifest;
|
|
32461
32569
|
for (const name of ["manifest.json", "manifest.webmanifest"]) {
|
|
32462
|
-
const manifestPath =
|
|
32463
|
-
if (
|
|
32570
|
+
const manifestPath = join13(rootDir, name);
|
|
32571
|
+
if (existsSync11(manifestPath)) {
|
|
32464
32572
|
try {
|
|
32465
|
-
const data = JSON.parse(
|
|
32573
|
+
const data = JSON.parse(readFileSync12(manifestPath, "utf-8"));
|
|
32466
32574
|
const icons = data.icons;
|
|
32467
32575
|
manifest = {
|
|
32468
32576
|
path: manifestPath,
|
|
@@ -32481,9 +32589,9 @@ function resolveWorkspace() {
|
|
|
32481
32589
|
rootPkg,
|
|
32482
32590
|
appName,
|
|
32483
32591
|
arcDir,
|
|
32484
|
-
browserDir:
|
|
32485
|
-
assetsDir:
|
|
32486
|
-
publicDir:
|
|
32592
|
+
browserDir: join13(arcDir, "browser"),
|
|
32593
|
+
assetsDir: join13(arcDir, "assets"),
|
|
32594
|
+
publicDir: join13(rootDir, "public"),
|
|
32487
32595
|
packages,
|
|
32488
32596
|
manifest
|
|
32489
32597
|
};
|
|
@@ -32492,73 +32600,120 @@ async function buildAll(ws, opts = {}) {
|
|
|
32492
32600
|
const cache = loadBuildCache(ws.arcDir);
|
|
32493
32601
|
const noCache = opts.noCache ?? false;
|
|
32494
32602
|
const themePath = ws.rootPkg.arc?.theme;
|
|
32603
|
+
const federation = getFederationConfig(ws);
|
|
32495
32604
|
log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
32496
32605
|
assertOneModulePerPackage(ws.packages);
|
|
32497
32606
|
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
32498
|
-
const serverDir =
|
|
32607
|
+
const serverDir = join13(ws.arcDir, "server");
|
|
32499
32608
|
const { entryFile: serverEntry, externals: serverExternals } = await buildServerApp(ws.rootDir, serverDir, ws.packages, cache, noCache);
|
|
32500
|
-
const accessMap = await extractAccessMap(ws.rootDir,
|
|
32501
|
-
|
|
32502
|
-
|
|
32609
|
+
const accessMap = await extractAccessMap(ws.rootDir, join13(serverDir, serverEntry));
|
|
32610
|
+
mkdirSync10(ws.arcDir, { recursive: true });
|
|
32611
|
+
writeFileSync10(join13(ws.arcDir, "access.json"), JSON.stringify(accessMap.access, null, 2) + `
|
|
32503
32612
|
`);
|
|
32504
|
-
const
|
|
32613
|
+
const isHost = Boolean(federation?.host);
|
|
32614
|
+
const moduleNameOf2 = (name) => name.split("/").pop() ?? name;
|
|
32615
|
+
const exposureOf = (pkgName) => accessMap.exposure[moduleNameOf2(pkgName)]?.exposure ?? "local";
|
|
32616
|
+
const localPackages = isHost ? ws.packages : ws.packages.filter((p) => exposureOf(p.name) !== "external");
|
|
32617
|
+
const fedPackages = ws.packages.filter((p) => ["external", "both"].includes(exposureOf(p.name)));
|
|
32618
|
+
const hasFederatedModules = fedPackages.length > 0;
|
|
32619
|
+
const needsPeerShell = isHost || hasFederatedModules;
|
|
32620
|
+
log2();
|
|
32621
|
+
const plan = planChunks(localPackages, accessMap.access);
|
|
32505
32622
|
ok(`Chunks: ${plan.chunks.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`).join(", ")}`);
|
|
32623
|
+
const fedPlan = hasFederatedModules ? planChunks(fedPackages, accessMap.access) : null;
|
|
32624
|
+
if (fedPlan) {
|
|
32625
|
+
ok(`Federated modules: ${fedPackages.map((p) => moduleNameOf2(p.name)).join(", ")}`);
|
|
32626
|
+
}
|
|
32506
32627
|
const i18nCollector = new Map;
|
|
32507
|
-
const [browserResult] = await Promise.all([
|
|
32508
|
-
buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector
|
|
32628
|
+
const [browserResult, , , , shellResult, fedResult] = await Promise.all([
|
|
32629
|
+
buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector, {
|
|
32630
|
+
federated: false,
|
|
32631
|
+
exposeRuntime: isHost
|
|
32632
|
+
}),
|
|
32509
32633
|
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
32510
32634
|
copyBrowserAssets(ws, cache, noCache),
|
|
32511
|
-
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache)
|
|
32635
|
+
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
|
|
32636
|
+
needsPeerShell ? buildPeerShell(ws.rootDir, join13(ws.arcDir, "shell"), cache, noCache) : Promise.resolve(undefined),
|
|
32637
|
+
fedPlan ? buildBrowserApp(ws.rootDir, join13(ws.arcDir, "browser-fed"), fedPlan, cache, noCache, i18nCollector, { federated: true, unitId: "browser-app-fed" }) : Promise.resolve(undefined)
|
|
32512
32638
|
]);
|
|
32513
32639
|
const { finalizeTranslations: finalizeTranslations3 } = await Promise.resolve().then(() => (init_i18n(), exports_i18n));
|
|
32514
32640
|
await finalizeTranslations3(ws.rootDir, ws.arcDir, i18nCollector);
|
|
32515
32641
|
collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages, [], serverExternals);
|
|
32516
32642
|
saveBuildCache(ws.arcDir, cache);
|
|
32517
|
-
const
|
|
32518
|
-
|
|
32643
|
+
const federationBuild = fedResult && hasFederatedModules ? {
|
|
32644
|
+
config: {
|
|
32645
|
+
slug: federation?.slug,
|
|
32646
|
+
auth: federation?.auth
|
|
32647
|
+
},
|
|
32648
|
+
modules: fedPackages.map((p) => moduleNameOf2(p.name)),
|
|
32649
|
+
permissions: [
|
|
32650
|
+
...new Set(fedPackages.flatMap((p) => accessMap.exposure[moduleNameOf2(p.name)]?.permissions ?? []))
|
|
32651
|
+
],
|
|
32652
|
+
slots: [
|
|
32653
|
+
...new Set(fedPackages.flatMap((p) => accessMap.exposure[moduleNameOf2(p.name)]?.slots ?? []))
|
|
32654
|
+
],
|
|
32655
|
+
build: {
|
|
32656
|
+
initial: fedResult.initial,
|
|
32657
|
+
groups: fedResult.groups,
|
|
32658
|
+
sharedChunks: fedResult.sharedChunks
|
|
32659
|
+
}
|
|
32660
|
+
} : undefined;
|
|
32661
|
+
const finalManifest = assembleManifest(ws, browserResult, cache, shellResult, federationBuild);
|
|
32662
|
+
writeFileSync10(join13(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
|
|
32519
32663
|
return finalManifest;
|
|
32520
32664
|
}
|
|
32521
|
-
function assembleManifest(ws, browser, cache) {
|
|
32665
|
+
function assembleManifest(ws, browser, cache, shell, federation) {
|
|
32522
32666
|
const stylesHash = cache.units["styles"]?.outputHash ?? "";
|
|
32523
32667
|
return {
|
|
32524
32668
|
initial: browser.initial,
|
|
32525
32669
|
groups: browser.groups,
|
|
32526
32670
|
sharedChunks: browser.sharedChunks,
|
|
32527
32671
|
stylesHash,
|
|
32528
|
-
buildTime: new Date().toISOString()
|
|
32672
|
+
buildTime: new Date().toISOString(),
|
|
32673
|
+
...shell ? { shell: { imports: shell.imports, peers: shell.peers } } : {},
|
|
32674
|
+
...federation ? { federation } : {}
|
|
32529
32675
|
};
|
|
32530
32676
|
}
|
|
32677
|
+
function getFederationConfig(ws) {
|
|
32678
|
+
const raw = ws.rootPkg.arc?.federation;
|
|
32679
|
+
if (!raw)
|
|
32680
|
+
return;
|
|
32681
|
+
if (raw.slug && !/^[a-z0-9-]+$/.test(raw.slug)) {
|
|
32682
|
+
throw new Error(`arc.federation.slug "${raw.slug}" \u2014 dozwolone tylko [a-z0-9-]+`);
|
|
32683
|
+
}
|
|
32684
|
+
return raw;
|
|
32685
|
+
}
|
|
32531
32686
|
function resolveAssetSource(from, pkgDir, rootDir) {
|
|
32532
32687
|
if (from.startsWith("./") || from.startsWith("../")) {
|
|
32533
|
-
const resolved =
|
|
32534
|
-
return
|
|
32688
|
+
const resolved = join13(pkgDir, from);
|
|
32689
|
+
return existsSync11(resolved) ? resolved : null;
|
|
32535
32690
|
}
|
|
32536
32691
|
const candidates = [
|
|
32537
|
-
|
|
32538
|
-
|
|
32692
|
+
join13(rootDir, "node_modules", from),
|
|
32693
|
+
join13(pkgDir, "node_modules", from)
|
|
32539
32694
|
];
|
|
32540
32695
|
for (const c of candidates) {
|
|
32541
|
-
if (
|
|
32696
|
+
if (existsSync11(c))
|
|
32542
32697
|
return c;
|
|
32543
32698
|
}
|
|
32544
|
-
const bunCacheDir =
|
|
32545
|
-
if (
|
|
32699
|
+
const bunCacheDir = join13(rootDir, "node_modules", ".bun");
|
|
32700
|
+
if (existsSync11(bunCacheDir)) {
|
|
32546
32701
|
for (const entry of readdirSync6(bunCacheDir, { withFileTypes: true })) {
|
|
32547
32702
|
if (!entry.isDirectory())
|
|
32548
32703
|
continue;
|
|
32549
|
-
const candidate =
|
|
32550
|
-
if (
|
|
32704
|
+
const candidate = join13(bunCacheDir, entry.name, "node_modules", from);
|
|
32705
|
+
if (existsSync11(candidate))
|
|
32551
32706
|
return candidate;
|
|
32552
32707
|
}
|
|
32553
32708
|
}
|
|
32554
32709
|
return null;
|
|
32555
32710
|
}
|
|
32556
32711
|
function readBrowserAssets(pkgDir) {
|
|
32557
|
-
const pkgJsonPath =
|
|
32558
|
-
if (!
|
|
32712
|
+
const pkgJsonPath = join13(pkgDir, "package.json");
|
|
32713
|
+
if (!existsSync11(pkgJsonPath))
|
|
32559
32714
|
return [];
|
|
32560
32715
|
try {
|
|
32561
|
-
const pkg = JSON.parse(
|
|
32716
|
+
const pkg = JSON.parse(readFileSync12(pkgJsonPath, "utf-8"));
|
|
32562
32717
|
const assets = pkg.arc?.browserAssets;
|
|
32563
32718
|
if (!Array.isArray(assets))
|
|
32564
32719
|
return [];
|
|
@@ -32568,14 +32723,14 @@ function readBrowserAssets(pkgDir) {
|
|
|
32568
32723
|
}
|
|
32569
32724
|
}
|
|
32570
32725
|
function discoverBrowserAssets(ws) {
|
|
32571
|
-
const arcDir =
|
|
32572
|
-
if (!
|
|
32726
|
+
const arcDir = join13(ws.rootDir, "node_modules", "@arcote.tech");
|
|
32727
|
+
if (!existsSync11(arcDir))
|
|
32573
32728
|
return [];
|
|
32574
32729
|
const out = [];
|
|
32575
32730
|
for (const entry of readdirSync6(arcDir, { withFileTypes: true })) {
|
|
32576
32731
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
32577
32732
|
continue;
|
|
32578
|
-
const pkgDir =
|
|
32733
|
+
const pkgDir = join13(arcDir, entry.name);
|
|
32579
32734
|
const assets = readBrowserAssets(pkgDir);
|
|
32580
32735
|
for (const asset of assets) {
|
|
32581
32736
|
const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
|
|
@@ -32589,7 +32744,7 @@ function discoverBrowserAssets(ws) {
|
|
|
32589
32744
|
return out;
|
|
32590
32745
|
}
|
|
32591
32746
|
async function copyBrowserAssets(ws, cache, noCache) {
|
|
32592
|
-
|
|
32747
|
+
mkdirSync10(ws.assetsDir, { recursive: true });
|
|
32593
32748
|
const assets = discoverBrowserAssets(ws);
|
|
32594
32749
|
if (assets.length === 0)
|
|
32595
32750
|
return;
|
|
@@ -32600,7 +32755,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
32600
32755
|
to: a.to,
|
|
32601
32756
|
mtime: mtimeOf(a.src)
|
|
32602
32757
|
})));
|
|
32603
|
-
const requiredOutputs = assets.map((a) =>
|
|
32758
|
+
const requiredOutputs = assets.map((a) => join13(ws.assetsDir, a.to));
|
|
32604
32759
|
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
32605
32760
|
console.log(` \u2713 cached: browser-assets (${assets.length})`);
|
|
32606
32761
|
return;
|
|
@@ -32608,10 +32763,10 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
32608
32763
|
console.log(` building: browser-assets (${assets.length})`);
|
|
32609
32764
|
const outputHashes = {};
|
|
32610
32765
|
for (const asset of assets) {
|
|
32611
|
-
const dest =
|
|
32612
|
-
|
|
32766
|
+
const dest = join13(ws.assetsDir, asset.to);
|
|
32767
|
+
mkdirSync10(dirname8(dest), { recursive: true });
|
|
32613
32768
|
copyFileSync(asset.src, dest);
|
|
32614
|
-
outputHashes[asset.to] = sha256Hex(
|
|
32769
|
+
outputHashes[asset.to] = sha256Hex(readFileSync12(dest));
|
|
32615
32770
|
}
|
|
32616
32771
|
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
32617
32772
|
}
|
|
@@ -32619,12 +32774,12 @@ async function loadServerContext(ws) {
|
|
|
32619
32774
|
globalThis.ONLY_SERVER = true;
|
|
32620
32775
|
globalThis.ONLY_BROWSER = false;
|
|
32621
32776
|
globalThis.ONLY_CLIENT = false;
|
|
32622
|
-
const platformDir =
|
|
32623
|
-
const platformPkg = JSON.parse(
|
|
32624
|
-
const platformEntry =
|
|
32777
|
+
const platformDir = join13(process.cwd(), "node_modules", "@arcote.tech", "platform");
|
|
32778
|
+
const platformPkg = JSON.parse(readFileSync12(join13(platformDir, "package.json"), "utf-8"));
|
|
32779
|
+
const platformEntry = join13(platformDir, platformPkg.main ?? "src/index.ts");
|
|
32625
32780
|
await import(platformEntry);
|
|
32626
|
-
const serverEntry =
|
|
32627
|
-
if (!
|
|
32781
|
+
const serverEntry = join13(ws.arcDir, "server", SERVER_ENTRY_FILE);
|
|
32782
|
+
if (!existsSync11(serverEntry)) {
|
|
32628
32783
|
return { context: null, moduleAccess: new Map };
|
|
32629
32784
|
}
|
|
32630
32785
|
try {
|
|
@@ -32648,24 +32803,26 @@ async function platformBuild(opts = {}) {
|
|
|
32648
32803
|
}
|
|
32649
32804
|
|
|
32650
32805
|
// src/commands/platform-deploy.ts
|
|
32651
|
-
import {
|
|
32652
|
-
import {
|
|
32806
|
+
import { randomBytes } from "crypto";
|
|
32807
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
32808
|
+
import { dirname as dirname11, join as join21 } from "path";
|
|
32653
32809
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
32654
32810
|
|
|
32655
32811
|
// src/deploy/bootstrap.ts
|
|
32656
32812
|
var {spawn: spawn4 } = globalThis.Bun;
|
|
32657
|
-
import { mkdirSync as
|
|
32813
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
|
|
32658
32814
|
import { tmpdir as tmpdir2 } from "os";
|
|
32659
|
-
import { dirname as dirname9, join as
|
|
32815
|
+
import { dirname as dirname9, join as join19 } from "path";
|
|
32660
32816
|
|
|
32661
32817
|
// src/deploy/ansible.ts
|
|
32662
32818
|
import { spawn as nodeSpawn } from "child_process";
|
|
32663
|
-
import { existsSync as
|
|
32819
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
32664
32820
|
import { homedir, tmpdir } from "os";
|
|
32665
|
-
import { join as
|
|
32821
|
+
import { join as join14 } from "path";
|
|
32666
32822
|
|
|
32667
32823
|
// src/deploy/assets.ts
|
|
32668
32824
|
var TERRAFORM_MAIN_TF = `terraform {
|
|
32825
|
+
required_version = ">= 1.1"
|
|
32669
32826
|
required_providers {
|
|
32670
32827
|
hcloud = {
|
|
32671
32828
|
source = "hetznercloud/hcloud"
|
|
@@ -32678,17 +32835,50 @@ provider "hcloud" {
|
|
|
32678
32835
|
token = var.hcloud_token
|
|
32679
32836
|
}
|
|
32680
32837
|
|
|
32838
|
+
locals {
|
|
32839
|
+
# cloud-init injects the operator public key into the default image user's
|
|
32840
|
+
# authorized_keys (root on Hetzner Ubuntu images) \u2014 matching the root-first
|
|
32841
|
+
# ansible bootstrap. Used only when use_cloud_init_key = true.
|
|
32842
|
+
cloud_init_user_data = <<-EOT
|
|
32843
|
+
#cloud-config
|
|
32844
|
+
ssh_pwauth: false
|
|
32845
|
+
chpasswd:
|
|
32846
|
+
expire: false
|
|
32847
|
+
ssh_authorized_keys:
|
|
32848
|
+
- \${trimspace(file(var.ssh_public_key))}
|
|
32849
|
+
# Hetzner marks the root password expired (must-change-on-login) when no
|
|
32850
|
+
# hcloud_ssh_key object is attached \u2014 which blocks non-interactive key
|
|
32851
|
+
# login ("password change required, no TTY"). Clear the expiry so the
|
|
32852
|
+
# deploy user / ansible can SSH in as root right away.
|
|
32853
|
+
runcmd:
|
|
32854
|
+
- chage -d $(date +%Y-%m-%d) -M -1 root
|
|
32855
|
+
EOT
|
|
32856
|
+
}
|
|
32857
|
+
|
|
32858
|
+
# Legacy path: a managed SSH key object in the Hetzner project. Skipped when
|
|
32859
|
+
# use_cloud_init_key = true, because several apps sharing one project with the
|
|
32860
|
+
# same local public key would collide on both key NAME and FINGERPRINT.
|
|
32681
32861
|
resource "hcloud_ssh_key" "deploy" {
|
|
32862
|
+
count = var.use_cloud_init_key ? 0 : 1
|
|
32682
32863
|
name = "\${var.server_name}-deploy"
|
|
32683
32864
|
public_key = file(var.ssh_public_key)
|
|
32684
32865
|
}
|
|
32685
32866
|
|
|
32867
|
+
# Re-home the pre-count address so an already-provisioned host (state created
|
|
32868
|
+
# before count existed) plans as a no-op instead of destroy+recreate \u2014 which
|
|
32869
|
+
# would ForceNew-replace the server. Ignored for fresh (empty) state.
|
|
32870
|
+
moved {
|
|
32871
|
+
from = hcloud_ssh_key.deploy
|
|
32872
|
+
to = hcloud_ssh_key.deploy[0]
|
|
32873
|
+
}
|
|
32874
|
+
|
|
32686
32875
|
resource "hcloud_server" "arc" {
|
|
32687
32876
|
name = var.server_name
|
|
32688
32877
|
image = var.server_image
|
|
32689
32878
|
server_type = var.server_type
|
|
32690
32879
|
location = var.server_location
|
|
32691
|
-
ssh_keys = [hcloud_ssh_key.deploy.id]
|
|
32880
|
+
ssh_keys = var.use_cloud_init_key ? [] : [hcloud_ssh_key.deploy[0].id]
|
|
32881
|
+
user_data = var.use_cloud_init_key ? local.cloud_init_user_data : null
|
|
32692
32882
|
|
|
32693
32883
|
public_net {
|
|
32694
32884
|
ipv4_enabled = true
|
|
@@ -32739,6 +32929,12 @@ variable "ssh_public_key" {
|
|
|
32739
32929
|
type = string
|
|
32740
32930
|
default = "~/.ssh/id_ed25519.pub"
|
|
32741
32931
|
}
|
|
32932
|
+
|
|
32933
|
+
variable "use_cloud_init_key" {
|
|
32934
|
+
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)."
|
|
32935
|
+
type = bool
|
|
32936
|
+
default = false
|
|
32937
|
+
}
|
|
32742
32938
|
`;
|
|
32743
32939
|
var ANSIBLE_SITE_YML = `---
|
|
32744
32940
|
# Arc platform bootstrap playbook \u2014 minimal hardened Docker host.
|
|
@@ -32935,30 +33131,30 @@ var ASSETS = {
|
|
|
32935
33131
|
}
|
|
32936
33132
|
};
|
|
32937
33133
|
async function materializeAssets(targetDir, files) {
|
|
32938
|
-
const { mkdirSync:
|
|
32939
|
-
const { join:
|
|
32940
|
-
|
|
33134
|
+
const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync11 } = await import("fs");
|
|
33135
|
+
const { join: join14 } = await import("path");
|
|
33136
|
+
mkdirSync11(targetDir, { recursive: true });
|
|
32941
33137
|
for (const [name, content] of Object.entries(files)) {
|
|
32942
|
-
|
|
33138
|
+
writeFileSync11(join14(targetDir, name), content);
|
|
32943
33139
|
}
|
|
32944
33140
|
}
|
|
32945
33141
|
|
|
32946
33142
|
// src/deploy/ansible.ts
|
|
32947
33143
|
function pickSshKeyForAnsible(configured) {
|
|
32948
33144
|
if (configured) {
|
|
32949
|
-
const expanded = configured.startsWith("~") ?
|
|
32950
|
-
return
|
|
33145
|
+
const expanded = configured.startsWith("~") ? join14(homedir(), configured.slice(1)) : configured;
|
|
33146
|
+
return existsSync12(expanded) ? expanded : null;
|
|
32951
33147
|
}
|
|
32952
33148
|
for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
|
|
32953
|
-
const path4 =
|
|
32954
|
-
if (
|
|
33149
|
+
const path4 = join14(homedir(), ".ssh", name);
|
|
33150
|
+
if (existsSync12(path4))
|
|
32955
33151
|
return path4;
|
|
32956
33152
|
}
|
|
32957
33153
|
return null;
|
|
32958
33154
|
}
|
|
32959
33155
|
async function runAnsible(inputs) {
|
|
32960
|
-
const workDir =
|
|
32961
|
-
|
|
33156
|
+
const workDir = join14(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
|
|
33157
|
+
mkdirSync11(workDir, { recursive: true });
|
|
32962
33158
|
await materializeAssets(workDir, ASSETS.ansible);
|
|
32963
33159
|
const user = inputs.asRoot ? "root" : inputs.target.user;
|
|
32964
33160
|
const port = inputs.ansible?.sshPort ?? inputs.target.port;
|
|
@@ -32974,7 +33170,7 @@ async function runAnsible(inputs) {
|
|
|
32974
33170
|
""
|
|
32975
33171
|
].join(`
|
|
32976
33172
|
`);
|
|
32977
|
-
|
|
33173
|
+
writeFileSync11(join14(workDir, "inventory.ini"), inventory);
|
|
32978
33174
|
const extraVarsJson = JSON.stringify({
|
|
32979
33175
|
username: inputs.target.user,
|
|
32980
33176
|
ssh_port: port,
|
|
@@ -34533,16 +34729,16 @@ function panelTimeseries(title, gridPos, query, legend, unit, datasource = PROME
|
|
|
34533
34729
|
// src/deploy/terraform.ts
|
|
34534
34730
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
34535
34731
|
import { createHash as createHash2 } from "crypto";
|
|
34536
|
-
import { existsSync as
|
|
34732
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
34537
34733
|
import { homedir as homedir2 } from "os";
|
|
34538
|
-
import { join as
|
|
34734
|
+
import { join as join15 } from "path";
|
|
34539
34735
|
async function runTerraform(inputs) {
|
|
34540
34736
|
const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
|
|
34541
|
-
const workDir =
|
|
34542
|
-
|
|
34737
|
+
const workDir = join15(homedir2(), ".arc-deploy", wsHash, "tf");
|
|
34738
|
+
mkdirSync12(workDir, { recursive: true });
|
|
34543
34739
|
await materializeAssets(workDir, ASSETS.terraform);
|
|
34544
34740
|
const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
|
|
34545
|
-
if (!
|
|
34741
|
+
if (!existsSync13(expandHome(sshPubKey))) {
|
|
34546
34742
|
throw new Error(`SSH public key not found at ${sshPubKey}. Set provision.terraform.sshPublicKey in deploy.arc.json.`);
|
|
34547
34743
|
}
|
|
34548
34744
|
const tfvars = [
|
|
@@ -34551,11 +34747,12 @@ async function runTerraform(inputs) {
|
|
|
34551
34747
|
`server_type = "${inputs.tf.serverType}"`,
|
|
34552
34748
|
`server_location = "${inputs.tf.location}"`,
|
|
34553
34749
|
`server_image = "${inputs.tf.image}"`,
|
|
34554
|
-
`ssh_public_key = "${expandHome(sshPubKey)}"
|
|
34750
|
+
`ssh_public_key = "${expandHome(sshPubKey)}"`,
|
|
34751
|
+
`use_cloud_init_key = ${inputs.useCloudInitKey}`
|
|
34555
34752
|
].join(`
|
|
34556
34753
|
`) + `
|
|
34557
34754
|
`;
|
|
34558
|
-
|
|
34755
|
+
writeFileSync12(join15(workDir, "terraform.tfvars"), tfvars);
|
|
34559
34756
|
await runTf(workDir, ["init", "-input=false", "-no-color"]);
|
|
34560
34757
|
await runTf(workDir, [
|
|
34561
34758
|
"apply",
|
|
@@ -34613,20 +34810,20 @@ function expandHome(p) {
|
|
|
34613
34810
|
}
|
|
34614
34811
|
|
|
34615
34812
|
// src/deploy/config.ts
|
|
34616
|
-
import { existsSync as
|
|
34617
|
-
import { join as
|
|
34813
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "fs";
|
|
34814
|
+
import { join as join17 } from "path";
|
|
34618
34815
|
|
|
34619
34816
|
// src/deploy/env-file.ts
|
|
34620
|
-
import { appendFileSync, existsSync as
|
|
34621
|
-
import { join as
|
|
34817
|
+
import { appendFileSync, existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
34818
|
+
import { join as join16 } from "path";
|
|
34622
34819
|
function loadDeployEnvFiles(rootDir, envNames) {
|
|
34623
|
-
const globalsPath =
|
|
34624
|
-
const globals =
|
|
34820
|
+
const globalsPath = join16(rootDir, "deploy.arc.env");
|
|
34821
|
+
const globals = existsSync14(globalsPath) ? parseEnvFile(readFileSync13(globalsPath, "utf-8"), globalsPath) : {};
|
|
34625
34822
|
const perEnv = {};
|
|
34626
34823
|
for (const name of envNames) {
|
|
34627
|
-
const envPath =
|
|
34628
|
-
if (
|
|
34629
|
-
perEnv[name] = parseEnvFile(
|
|
34824
|
+
const envPath = join16(rootDir, `deploy.arc.${name}.env`);
|
|
34825
|
+
if (existsSync14(envPath)) {
|
|
34826
|
+
perEnv[name] = parseEnvFile(readFileSync13(envPath, "utf-8"), envPath);
|
|
34630
34827
|
}
|
|
34631
34828
|
}
|
|
34632
34829
|
return { globals, perEnv };
|
|
@@ -34642,16 +34839,16 @@ function ensurePersistedSecret(rootDir, scope, key, generate) {
|
|
|
34642
34839
|
if (process.env[key])
|
|
34643
34840
|
return process.env[key];
|
|
34644
34841
|
const fileName = scope === "globals" ? "deploy.arc.env" : `deploy.arc.${scope}.env`;
|
|
34645
|
-
const path4 =
|
|
34646
|
-
if (
|
|
34647
|
-
const existing = parseEnvFile(
|
|
34842
|
+
const path4 = join16(rootDir, fileName);
|
|
34843
|
+
if (existsSync14(path4)) {
|
|
34844
|
+
const existing = parseEnvFile(readFileSync13(path4, "utf-8"), path4);
|
|
34648
34845
|
if (existing[key]) {
|
|
34649
34846
|
process.env[key] = existing[key];
|
|
34650
34847
|
return existing[key];
|
|
34651
34848
|
}
|
|
34652
34849
|
}
|
|
34653
34850
|
const value = generate();
|
|
34654
|
-
const prefix =
|
|
34851
|
+
const prefix = existsSync14(path4) && !readFileSync13(path4, "utf-8").endsWith(`
|
|
34655
34852
|
`) ? `
|
|
34656
34853
|
` : "";
|
|
34657
34854
|
appendFileSync(path4, `${prefix}${key}=${value}
|
|
@@ -34687,17 +34884,17 @@ function parseEnvFile(content, pathForErrors) {
|
|
|
34687
34884
|
// src/deploy/config.ts
|
|
34688
34885
|
var DEPLOY_CONFIG_FILE = "deploy.arc.json";
|
|
34689
34886
|
function deployConfigPath(rootDir) {
|
|
34690
|
-
return
|
|
34887
|
+
return join17(rootDir, DEPLOY_CONFIG_FILE);
|
|
34691
34888
|
}
|
|
34692
34889
|
function deployConfigExists(rootDir) {
|
|
34693
|
-
return
|
|
34890
|
+
return existsSync15(deployConfigPath(rootDir));
|
|
34694
34891
|
}
|
|
34695
34892
|
function loadDeployConfig(rootDir) {
|
|
34696
34893
|
const path4 = deployConfigPath(rootDir);
|
|
34697
|
-
if (!
|
|
34894
|
+
if (!existsSync15(path4)) {
|
|
34698
34895
|
throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
|
|
34699
34896
|
}
|
|
34700
|
-
const raw =
|
|
34897
|
+
const raw = readFileSync14(path4, "utf-8");
|
|
34701
34898
|
let parsed;
|
|
34702
34899
|
try {
|
|
34703
34900
|
parsed = JSON.parse(raw);
|
|
@@ -34720,9 +34917,9 @@ function loadDeployConfig(rootDir) {
|
|
|
34720
34917
|
}
|
|
34721
34918
|
function saveDeployConfig(rootDir, cfg) {
|
|
34722
34919
|
const path4 = deployConfigPath(rootDir);
|
|
34723
|
-
const raw =
|
|
34920
|
+
const raw = existsSync15(path4) ? JSON.parse(readFileSync14(path4, "utf-8")) : {};
|
|
34724
34921
|
raw.target = { ...raw.target, ...cfg.target };
|
|
34725
|
-
|
|
34922
|
+
writeFileSync13(path4, JSON.stringify(raw, null, 2) + `
|
|
34726
34923
|
`);
|
|
34727
34924
|
}
|
|
34728
34925
|
var VAR_REGEX = /\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g;
|
|
@@ -34757,9 +34954,20 @@ function validateDeployConfig(input) {
|
|
|
34757
34954
|
if (!/^[A-Z][A-Z0-9_]*$/.test(passwordEnv)) {
|
|
34758
34955
|
throw new Error(`deploy.arc.json: registry.passwordEnv "${passwordEnv}" must be an UPPER_SNAKE_CASE env var name`);
|
|
34759
34956
|
}
|
|
34957
|
+
const provisionRaw = input.provision;
|
|
34958
|
+
const hasProvisionTf = isObject(provisionRaw) && isObject(provisionRaw.terraform);
|
|
34959
|
+
const hostRaw = optionalString(target, "target.host");
|
|
34960
|
+
let host;
|
|
34961
|
+
if (hostRaw && hostRaw.length > 0) {
|
|
34962
|
+
host = hostRaw;
|
|
34963
|
+
} else if (hasProvisionTf) {
|
|
34964
|
+
host = "PENDING_TERRAFORM";
|
|
34965
|
+
} else {
|
|
34966
|
+
throw new Error(`deploy.arc.json: target.host is required unless provision.terraform is present (with provision, terraform fills it on first deploy).`);
|
|
34967
|
+
}
|
|
34760
34968
|
const validated = {
|
|
34761
34969
|
target: {
|
|
34762
|
-
host
|
|
34970
|
+
host,
|
|
34763
34971
|
user: optionalString(target, "target.user") ?? "deploy",
|
|
34764
34972
|
port: optionalNumber(target, "target.port") ?? 22,
|
|
34765
34973
|
remoteDir: optionalString(target, "target.remoteDir") ?? "/opt/arc",
|
|
@@ -34860,13 +35068,23 @@ function validateDeployConfig(input) {
|
|
|
34860
35068
|
if (providerVal !== "hcloud") {
|
|
34861
35069
|
throw new Error(`deploy.arc.json: provision.terraform.provider must be "hcloud" (got "${providerVal}")`);
|
|
34862
35070
|
}
|
|
35071
|
+
const serverName = optionalString(tf, "provision.terraform.serverName");
|
|
35072
|
+
if (serverName !== undefined && !/^[a-z0-9-]{1,63}$/.test(serverName)) {
|
|
35073
|
+
throw new Error(`deploy.arc.json: provision.terraform.serverName "${serverName}" must match [a-z0-9-] and be at most 63 chars`);
|
|
35074
|
+
}
|
|
35075
|
+
const sshKeyStrategyRaw = optionalString(tf, "provision.terraform.sshKeyStrategy");
|
|
35076
|
+
if (sshKeyStrategyRaw !== undefined && sshKeyStrategyRaw !== "resource" && sshKeyStrategyRaw !== "cloud-init") {
|
|
35077
|
+
throw new Error(`deploy.arc.json: provision.terraform.sshKeyStrategy must be "resource" or "cloud-init" (got "${sshKeyStrategyRaw}")`);
|
|
35078
|
+
}
|
|
34863
35079
|
const terraform = {
|
|
34864
35080
|
provider: "hcloud",
|
|
34865
35081
|
serverType: requireString(tf, "provision.terraform.serverType"),
|
|
34866
35082
|
location: requireString(tf, "provision.terraform.location"),
|
|
34867
35083
|
image: optionalString(tf, "provision.terraform.image") ?? "ubuntu-22.04",
|
|
34868
35084
|
tokenEnv: requireString(tf, "provision.terraform.tokenEnv"),
|
|
34869
|
-
sshPublicKey: optionalString(tf, "provision.terraform.sshPublicKey")
|
|
35085
|
+
sshPublicKey: optionalString(tf, "provision.terraform.sshPublicKey"),
|
|
35086
|
+
serverName,
|
|
35087
|
+
sshKeyStrategy: sshKeyStrategyRaw
|
|
34870
35088
|
};
|
|
34871
35089
|
let ansible;
|
|
34872
35090
|
const ansibleRaw = provision.ansible;
|
|
@@ -34936,17 +35154,17 @@ function cfgErr(path4, expected) {
|
|
|
34936
35154
|
|
|
34937
35155
|
// src/deploy/ssh.ts
|
|
34938
35156
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
34939
|
-
import { existsSync as
|
|
35157
|
+
import { existsSync as existsSync16 } from "fs";
|
|
34940
35158
|
import { homedir as homedir3 } from "os";
|
|
34941
|
-
import { join as
|
|
35159
|
+
import { join as join18 } from "path";
|
|
34942
35160
|
function pickSshKey(target) {
|
|
34943
35161
|
if (target.sshKey) {
|
|
34944
|
-
const expanded = target.sshKey.startsWith("~") ?
|
|
34945
|
-
return
|
|
35162
|
+
const expanded = target.sshKey.startsWith("~") ? join18(homedir3(), target.sshKey.slice(1)) : target.sshKey;
|
|
35163
|
+
return existsSync16(expanded) ? expanded : null;
|
|
34946
35164
|
}
|
|
34947
35165
|
for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
|
|
34948
|
-
const path4 =
|
|
34949
|
-
if (
|
|
35166
|
+
const path4 = join18(homedir3(), ".ssh", name);
|
|
35167
|
+
if (existsSync16(path4))
|
|
34950
35168
|
return path4;
|
|
34951
35169
|
}
|
|
34952
35170
|
return null;
|
|
@@ -34961,7 +35179,7 @@ function sshMuxArgs() {
|
|
|
34961
35179
|
"-o",
|
|
34962
35180
|
"ControlMaster=auto",
|
|
34963
35181
|
"-o",
|
|
34964
|
-
`ControlPath=${
|
|
35182
|
+
`ControlPath=${join18(homedir3(), ".ssh", "cm-arc-%C")}`,
|
|
34965
35183
|
"-o",
|
|
34966
35184
|
"ControlPersist=120"
|
|
34967
35185
|
];
|
|
@@ -35161,10 +35379,14 @@ async function bootstrap(inputs) {
|
|
|
35161
35379
|
if (!token) {
|
|
35162
35380
|
throw new Error(`Environment variable ${cfg.provision.terraform.tokenEnv} is not set`);
|
|
35163
35381
|
}
|
|
35382
|
+
const firstEnv = Object.keys(cfg.envs)[0] ?? "host";
|
|
35383
|
+
const serverName = cfg.provision.terraform.serverName ?? `arc-${firstEnv}`;
|
|
35384
|
+
const useCloudInitKey = cfg.provision.terraform.sshKeyStrategy === "cloud-init";
|
|
35164
35385
|
const tfOut = await runTerraform({
|
|
35165
35386
|
tf: cfg.provision.terraform,
|
|
35166
35387
|
token,
|
|
35167
|
-
serverName
|
|
35388
|
+
serverName,
|
|
35389
|
+
useCloudInitKey,
|
|
35168
35390
|
workspaceDir: rootDir
|
|
35169
35391
|
});
|
|
35170
35392
|
ok(`Server provisioned: ${tfOut.serverIp}`);
|
|
@@ -35226,17 +35448,17 @@ async function isRegistryRunning(cfg) {
|
|
|
35226
35448
|
}
|
|
35227
35449
|
async function upStack(inputs) {
|
|
35228
35450
|
const { cfg } = inputs;
|
|
35229
|
-
const workDir =
|
|
35230
|
-
|
|
35451
|
+
const workDir = join19(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
|
|
35452
|
+
mkdirSync13(workDir, { recursive: true });
|
|
35231
35453
|
await assertRegistryDnsResolves(cfg);
|
|
35232
35454
|
const password = process.env[cfg.registry.passwordEnv];
|
|
35233
35455
|
if (!password) {
|
|
35234
35456
|
throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set. ` + `Set it (e.g. \`export ${cfg.registry.passwordEnv}=...\`) before bootstrap.`);
|
|
35235
35457
|
}
|
|
35236
35458
|
const htpasswdLine = await generateHtpasswd(cfg.registry.username, password);
|
|
35237
|
-
|
|
35238
|
-
|
|
35239
|
-
|
|
35459
|
+
writeFileSync14(join19(workDir, "htpasswd"), htpasswdLine);
|
|
35460
|
+
writeFileSync14(join19(workDir, "Caddyfile"), generateCaddyfile(cfg));
|
|
35461
|
+
writeFileSync14(join19(workDir, "docker-compose.yml"), generateCompose({ cfg }));
|
|
35240
35462
|
let observabilityFiles = null;
|
|
35241
35463
|
let observabilityHtpasswd = null;
|
|
35242
35464
|
if (cfg.observability?.enabled) {
|
|
@@ -35248,30 +35470,30 @@ async function upStack(inputs) {
|
|
|
35248
35470
|
}
|
|
35249
35471
|
observabilityHtpasswd = await generateCaddyBasicAuthLine("admin", adminPassword);
|
|
35250
35472
|
for (const [relPath, contents] of Object.entries(observabilityFiles)) {
|
|
35251
|
-
const fullPath =
|
|
35252
|
-
|
|
35253
|
-
|
|
35473
|
+
const fullPath = join19(workDir, relPath);
|
|
35474
|
+
mkdirSync13(dirname9(fullPath), { recursive: true });
|
|
35475
|
+
writeFileSync14(fullPath, contents);
|
|
35254
35476
|
}
|
|
35255
|
-
|
|
35477
|
+
writeFileSync14(join19(workDir, "observability-htpasswd"), observabilityHtpasswd);
|
|
35256
35478
|
}
|
|
35257
35479
|
await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
|
|
35258
35480
|
for (const name of Object.keys(cfg.envs)) {
|
|
35259
35481
|
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
|
|
35260
35482
|
}
|
|
35261
35483
|
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,
|
|
35484
|
+
await scpUpload(cfg.target, join19(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
|
|
35485
|
+
await scpUpload(cfg.target, join19(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
|
|
35486
|
+
await scpUpload(cfg.target, join19(workDir, "htpasswd"), `${cfg.target.remoteDir}/registry-auth/htpasswd`);
|
|
35265
35487
|
if (observabilityFiles && observabilityHtpasswd) {
|
|
35266
35488
|
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/observability/grafana-dashboards ${cfg.target.remoteDir}/observability/grafana-alerting`);
|
|
35267
35489
|
for (const relPath of Object.keys(observabilityFiles)) {
|
|
35268
|
-
const localDir = dirname9(
|
|
35269
|
-
|
|
35490
|
+
const localDir = dirname9(join19(workDir, relPath));
|
|
35491
|
+
mkdirSync13(localDir, { recursive: true });
|
|
35270
35492
|
}
|
|
35271
35493
|
for (const relPath of Object.keys(observabilityFiles)) {
|
|
35272
|
-
await scpUpload(cfg.target,
|
|
35494
|
+
await scpUpload(cfg.target, join19(workDir, relPath), `${cfg.target.remoteDir}/${relPath}`);
|
|
35273
35495
|
}
|
|
35274
|
-
await scpUpload(cfg.target,
|
|
35496
|
+
await scpUpload(cfg.target, join19(workDir, "observability-htpasswd"), `${cfg.target.remoteDir}/observability-htpasswd`);
|
|
35275
35497
|
}
|
|
35276
35498
|
await assertExec(cfg.target, `touch ${cfg.target.remoteDir}/.env`);
|
|
35277
35499
|
if (cfg.observability?.enabled) {
|
|
@@ -35426,14 +35648,14 @@ var {spawn: spawn5 } = globalThis.Bun;
|
|
|
35426
35648
|
import { createHash as createHash3 } from "crypto";
|
|
35427
35649
|
import {
|
|
35428
35650
|
copyFileSync as copyFileSync2,
|
|
35429
|
-
existsSync as
|
|
35430
|
-
mkdirSync as
|
|
35431
|
-
readFileSync as
|
|
35651
|
+
existsSync as existsSync17,
|
|
35652
|
+
mkdirSync as mkdirSync14,
|
|
35653
|
+
readFileSync as readFileSync15,
|
|
35432
35654
|
realpathSync as realpathSync2,
|
|
35433
|
-
writeFileSync as
|
|
35655
|
+
writeFileSync as writeFileSync15
|
|
35434
35656
|
} from "fs";
|
|
35435
35657
|
import { tmpdir as tmpdir3 } from "os";
|
|
35436
|
-
import { dirname as dirname10, join as
|
|
35658
|
+
import { dirname as dirname10, join as join20 } from "path";
|
|
35437
35659
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
35438
35660
|
|
|
35439
35661
|
// src/deploy/image-template.ts
|
|
@@ -35483,8 +35705,8 @@ function generateDockerfile(inputs) {
|
|
|
35483
35705
|
// src/deploy/image.ts
|
|
35484
35706
|
async function buildImage(ws, opts) {
|
|
35485
35707
|
await ensureDocker();
|
|
35486
|
-
const manifestPath =
|
|
35487
|
-
if (!
|
|
35708
|
+
const manifestPath = join20(ws.arcDir, "manifest.json");
|
|
35709
|
+
if (!existsSync17(manifestPath)) {
|
|
35488
35710
|
throw new Error(`No build manifest at ${manifestPath}. Run \`arc platform build\` first or omit --skip-build.`);
|
|
35489
35711
|
}
|
|
35490
35712
|
embedCliBundle(ws);
|
|
@@ -35494,10 +35716,10 @@ async function buildImage(ws, opts) {
|
|
|
35494
35716
|
const dockerfileInputs = collectDockerfileInputs(ws);
|
|
35495
35717
|
const dockerfile = generateDockerfile(dockerfileInputs);
|
|
35496
35718
|
const buildContextDir = ws.rootDir;
|
|
35497
|
-
const dockerfileDir =
|
|
35498
|
-
|
|
35499
|
-
const dockerfilePath =
|
|
35500
|
-
|
|
35719
|
+
const dockerfileDir = join20(tmpdir3(), `arc-image-${Date.now()}`);
|
|
35720
|
+
mkdirSync14(dockerfileDir, { recursive: true });
|
|
35721
|
+
const dockerfilePath = join20(dockerfileDir, "Dockerfile");
|
|
35722
|
+
writeFileSync15(dockerfilePath, dockerfile);
|
|
35501
35723
|
const buildArgs = [
|
|
35502
35724
|
"build",
|
|
35503
35725
|
"--platform=linux/amd64",
|
|
@@ -35522,20 +35744,20 @@ async function buildImage(ws, opts) {
|
|
|
35522
35744
|
}
|
|
35523
35745
|
function embedCliBundle(ws) {
|
|
35524
35746
|
const source = locateCliBundle();
|
|
35525
|
-
const target =
|
|
35747
|
+
const target = join20(ws.arcDir, "host.js");
|
|
35526
35748
|
copyFileSync2(source, target);
|
|
35527
35749
|
}
|
|
35528
35750
|
function locateCliBundle() {
|
|
35529
35751
|
const here = fileURLToPath7(import.meta.url);
|
|
35530
35752
|
let cur = dirname10(here);
|
|
35531
35753
|
while (cur !== "/" && cur !== "") {
|
|
35532
|
-
const candidate =
|
|
35533
|
-
if (
|
|
35754
|
+
const candidate = join20(cur, "package.json");
|
|
35755
|
+
if (existsSync17(candidate)) {
|
|
35534
35756
|
try {
|
|
35535
|
-
const pkg = JSON.parse(
|
|
35757
|
+
const pkg = JSON.parse(readFileSync15(candidate, "utf-8"));
|
|
35536
35758
|
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
35537
|
-
const distIndex =
|
|
35538
|
-
if (!
|
|
35759
|
+
const distIndex = join20(realpathSync2(cur), "dist", "index.js");
|
|
35760
|
+
if (!existsSync17(distIndex)) {
|
|
35539
35761
|
throw new Error(`arc-cli bundle missing at ${distIndex}. Run \`bun run build\` in packages/cli/.`);
|
|
35540
35762
|
}
|
|
35541
35763
|
return distIndex;
|
|
@@ -35567,17 +35789,17 @@ async function ensureDocker() {
|
|
|
35567
35789
|
}
|
|
35568
35790
|
}
|
|
35569
35791
|
function computeContentHash(manifestPath) {
|
|
35570
|
-
const raw = JSON.parse(
|
|
35792
|
+
const raw = JSON.parse(readFileSync15(manifestPath, "utf-8"));
|
|
35571
35793
|
delete raw.buildTime;
|
|
35572
35794
|
const canonical = JSON.stringify(raw);
|
|
35573
35795
|
return createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
35574
35796
|
}
|
|
35575
35797
|
function collectDockerfileInputs(ws) {
|
|
35576
|
-
const hasPublicDir =
|
|
35577
|
-
const hasLocales =
|
|
35798
|
+
const hasPublicDir = existsSync17(join20(ws.rootDir, "public"));
|
|
35799
|
+
const hasLocales = existsSync17(join20(ws.rootDir, "locales"));
|
|
35578
35800
|
let manifestPath;
|
|
35579
35801
|
for (const name of ["manifest.webmanifest", "manifest.json"]) {
|
|
35580
|
-
if (
|
|
35802
|
+
if (existsSync17(join20(ws.rootDir, name))) {
|
|
35581
35803
|
manifestPath = name;
|
|
35582
35804
|
break;
|
|
35583
35805
|
}
|
|
@@ -36192,7 +36414,7 @@ ${import_picocolors2.default.gray(m2)} ${s}
|
|
|
36192
36414
|
};
|
|
36193
36415
|
|
|
36194
36416
|
// src/deploy/survey.ts
|
|
36195
|
-
async function runSurvey() {
|
|
36417
|
+
async function runSurvey(appName) {
|
|
36196
36418
|
we("arc platform deploy \u2014 initial setup");
|
|
36197
36419
|
const mode = await de({
|
|
36198
36420
|
message: "How should deploy reach the target server?",
|
|
@@ -36294,12 +36516,17 @@ async function runSurvey() {
|
|
|
36294
36516
|
});
|
|
36295
36517
|
if (BD(location))
|
|
36296
36518
|
cancel();
|
|
36519
|
+
const firstEnv = Object.keys(envs)[0] ?? "host";
|
|
36520
|
+
const appSlug = sanitizeImageName(appName ?? "arc-app").replace(/[^a-z0-9-]+/g, "-");
|
|
36521
|
+
const serverName = `arc-${appSlug}-${firstEnv}`.slice(0, 63);
|
|
36297
36522
|
const terraform = {
|
|
36298
36523
|
provider: "hcloud",
|
|
36299
36524
|
serverType,
|
|
36300
36525
|
location,
|
|
36301
36526
|
image: "ubuntu-22.04",
|
|
36302
|
-
tokenEnv
|
|
36527
|
+
tokenEnv,
|
|
36528
|
+
serverName,
|
|
36529
|
+
sshKeyStrategy: "cloud-init"
|
|
36303
36530
|
};
|
|
36304
36531
|
provision = { terraform };
|
|
36305
36532
|
}
|
|
@@ -36384,12 +36611,15 @@ function cancel() {
|
|
|
36384
36611
|
}
|
|
36385
36612
|
|
|
36386
36613
|
// src/commands/platform-deploy.ts
|
|
36614
|
+
function federationExposesModules(ws) {
|
|
36615
|
+
return Boolean(ws.rootPkg?.arc?.federation?.slug);
|
|
36616
|
+
}
|
|
36387
36617
|
async function platformDeploy(envArg, options = {}) {
|
|
36388
36618
|
const ws = resolveWorkspace();
|
|
36389
36619
|
let cfg;
|
|
36390
36620
|
if (!deployConfigExists(ws.rootDir)) {
|
|
36391
36621
|
log2("No deploy.arc.json found \u2014 launching survey.");
|
|
36392
|
-
cfg = await runSurvey();
|
|
36622
|
+
cfg = await runSurvey(ws.appName);
|
|
36393
36623
|
saveDeployConfig(ws.rootDir, cfg);
|
|
36394
36624
|
ok("Saved deploy.arc.json");
|
|
36395
36625
|
}
|
|
@@ -36398,6 +36628,12 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
36398
36628
|
for (const pg of pgEnvs) {
|
|
36399
36629
|
ensurePersistedSecret(ws.rootDir, pg.name, pg.passwordKey, () => crypto.randomUUID().replace(/-/g, ""));
|
|
36400
36630
|
}
|
|
36631
|
+
if (federationExposesModules(ws)) {
|
|
36632
|
+
for (const name of Object.keys(cfg.envs)) {
|
|
36633
|
+
ensurePersistedSecret(ws.rootDir, name, "ARC_APP_SECRET", () => randomBytes(32).toString("base64url"));
|
|
36634
|
+
ensurePersistedSecret(ws.rootDir, name, "ARC_PAIRING_TOKEN", () => randomBytes(16).toString("base64url"));
|
|
36635
|
+
}
|
|
36636
|
+
}
|
|
36401
36637
|
if (cfg.observability?.enabled) {
|
|
36402
36638
|
const key = cfg.observability.adminPasswordEnv ?? "ARC_OBSERVABILITY_PASSWORD";
|
|
36403
36639
|
ensurePersistedSecret(ws.rootDir, "globals", key, () => crypto.randomUUID().replace(/-/g, ""));
|
|
@@ -36406,14 +36642,14 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
36406
36642
|
err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
|
|
36407
36643
|
process.exit(1);
|
|
36408
36644
|
})() : Object.keys(cfg.envs);
|
|
36409
|
-
const manifestPath =
|
|
36645
|
+
const manifestPath = join21(ws.arcDir, "manifest.json");
|
|
36410
36646
|
if (!options.imageTag) {
|
|
36411
|
-
const needBuild = options.rebuild || !
|
|
36647
|
+
const needBuild = options.rebuild || !existsSync18(manifestPath);
|
|
36412
36648
|
if (needBuild && !options.skipBuild) {
|
|
36413
36649
|
log2("Building platform...");
|
|
36414
36650
|
await buildAll(ws, { noCache: options.rebuild });
|
|
36415
36651
|
ok("Build complete");
|
|
36416
|
-
} else if (!
|
|
36652
|
+
} else if (!existsSync18(manifestPath)) {
|
|
36417
36653
|
err("No build found and --skip-build was set.");
|
|
36418
36654
|
process.exit(1);
|
|
36419
36655
|
}
|
|
@@ -36483,9 +36719,9 @@ function readCliVersion2() {
|
|
|
36483
36719
|
let cur = dirname11(fileURLToPath8(import.meta.url));
|
|
36484
36720
|
const root = dirname11(cur).startsWith("/") ? "/" : ".";
|
|
36485
36721
|
while (cur !== root && cur !== "") {
|
|
36486
|
-
const candidate =
|
|
36487
|
-
if (
|
|
36488
|
-
const pkg = JSON.parse(
|
|
36722
|
+
const candidate = join21(cur, "package.json");
|
|
36723
|
+
if (existsSync18(candidate)) {
|
|
36724
|
+
const pkg = JSON.parse(readFileSync16(candidate, "utf-8"));
|
|
36489
36725
|
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
36490
36726
|
return pkg.version ?? "unknown";
|
|
36491
36727
|
}
|
|
@@ -36501,8 +36737,8 @@ function readCliVersion2() {
|
|
|
36501
36737
|
}
|
|
36502
36738
|
}
|
|
36503
36739
|
async function hashDeployConfig(rootDir) {
|
|
36504
|
-
const p2 =
|
|
36505
|
-
const content =
|
|
36740
|
+
const p2 = join21(rootDir, "deploy.arc.json");
|
|
36741
|
+
const content = readFileSync16(p2);
|
|
36506
36742
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
36507
36743
|
hasher.update(content);
|
|
36508
36744
|
hasher.update(readCliVersion2());
|
|
@@ -36510,8 +36746,9 @@ async function hashDeployConfig(rootDir) {
|
|
|
36510
36746
|
}
|
|
36511
36747
|
|
|
36512
36748
|
// src/platform/startup.ts
|
|
36513
|
-
import {
|
|
36514
|
-
import {
|
|
36749
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
36750
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync16, readFileSync as readFileSync17, watch, writeFileSync as writeFileSync16 } from "fs";
|
|
36751
|
+
import { join as join23 } from "path";
|
|
36515
36752
|
|
|
36516
36753
|
// ../host/src/connection-manager.ts
|
|
36517
36754
|
class ConnectionManager {
|
|
@@ -37944,8 +38181,10 @@ async function createArcServer(config) {
|
|
|
37944
38181
|
const cronScheduler = new CronScheduler(contextHandler);
|
|
37945
38182
|
cronScheduler.start();
|
|
37946
38183
|
const connectionManager = new ConnectionManager;
|
|
38184
|
+
const messageChains = new Map;
|
|
37947
38185
|
const coep = config.coep ?? "unsafe-none";
|
|
37948
38186
|
const corsOrigins = config.corsOrigins;
|
|
38187
|
+
const allowPrivateNetwork = config.allowPrivateNetwork ?? false;
|
|
37949
38188
|
const maxRequestBodySize = config.maxRequestBodySize ?? 10 * 1024 * 1024;
|
|
37950
38189
|
const securityHeaders = {
|
|
37951
38190
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -37957,7 +38196,7 @@ async function createArcServer(config) {
|
|
|
37957
38196
|
const origin = req?.headers.get("Origin") || null;
|
|
37958
38197
|
const headers = {
|
|
37959
38198
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
37960
|
-
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope, X-Arc-Tokens",
|
|
38199
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope, X-Arc-Tokens, X-Arc-Pairing-Token",
|
|
37961
38200
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
37962
38201
|
"Cross-Origin-Embedder-Policy": coep,
|
|
37963
38202
|
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
@@ -37994,7 +38233,8 @@ async function createArcServer(config) {
|
|
|
37994
38233
|
const url = new URL(req.url);
|
|
37995
38234
|
const corsHeaders2 = buildCorsHeaders(req);
|
|
37996
38235
|
if (req.method === "OPTIONS") {
|
|
37997
|
-
|
|
38236
|
+
const pna = allowPrivateNetwork && req.headers.get("Access-Control-Request-Private-Network") ? { "Access-Control-Allow-Private-Network": "true" } : {};
|
|
38237
|
+
return new Response(null, { headers: { ...corsHeaders2, ...pna } });
|
|
37998
38238
|
}
|
|
37999
38239
|
const handleRequest = async (span) => {
|
|
38000
38240
|
const contentLength = Number(req.headers.get("Content-Length") ?? 0);
|
|
@@ -38082,29 +38322,38 @@ async function createArcServer(config) {
|
|
|
38082
38322
|
console.error("WS handler error:", error);
|
|
38083
38323
|
}
|
|
38084
38324
|
};
|
|
38085
|
-
const
|
|
38086
|
-
|
|
38087
|
-
|
|
38088
|
-
|
|
38089
|
-
|
|
38090
|
-
const carrier = {};
|
|
38091
|
-
if (typeof message?.traceparent === "string")
|
|
38092
|
-
carrier.traceparent = message.traceparent;
|
|
38093
|
-
if (typeof message?.tracestate === "string")
|
|
38094
|
-
carrier.tracestate = message.tracestate;
|
|
38095
|
-
await telemetry.runWithExtractedContext(carrier, () => telemetry.startSpan(`ws.${message?.type ?? "message"}`, () => dispatch(), {
|
|
38096
|
-
kind: 2,
|
|
38097
|
-
attributes: {
|
|
38098
|
-
"messaging.system": "arc-ws",
|
|
38099
|
-
"messaging.operation": "process",
|
|
38100
|
-
"messaging.message.type": message?.type,
|
|
38101
|
-
"arc.ws.client_id": client.id
|
|
38325
|
+
const processMessage = async () => {
|
|
38326
|
+
const telemetry = config.telemetry;
|
|
38327
|
+
if (!telemetry) {
|
|
38328
|
+
await dispatch();
|
|
38329
|
+
return;
|
|
38102
38330
|
}
|
|
38103
|
-
|
|
38331
|
+
const carrier = {};
|
|
38332
|
+
if (typeof message?.traceparent === "string")
|
|
38333
|
+
carrier.traceparent = message.traceparent;
|
|
38334
|
+
if (typeof message?.tracestate === "string")
|
|
38335
|
+
carrier.tracestate = message.tracestate;
|
|
38336
|
+
await telemetry.runWithExtractedContext(carrier, () => telemetry.startSpan(`ws.${message?.type ?? "message"}`, () => dispatch(), {
|
|
38337
|
+
kind: 2,
|
|
38338
|
+
attributes: {
|
|
38339
|
+
"messaging.system": "arc-ws",
|
|
38340
|
+
"messaging.operation": "process",
|
|
38341
|
+
"messaging.message.type": message?.type,
|
|
38342
|
+
"arc.ws.client_id": client.id
|
|
38343
|
+
}
|
|
38344
|
+
}));
|
|
38345
|
+
};
|
|
38346
|
+
const prev = messageChains.get(client.id) ?? Promise.resolve();
|
|
38347
|
+
const next = prev.then(processMessage, processMessage).catch((error) => {
|
|
38348
|
+
console.error("WS message chain error:", error);
|
|
38349
|
+
});
|
|
38350
|
+
messageChains.set(client.id, next);
|
|
38351
|
+
await next;
|
|
38104
38352
|
},
|
|
38105
38353
|
close(ws) {
|
|
38106
38354
|
const client = connectionManager.getClientByWs(ws);
|
|
38107
38355
|
if (client) {
|
|
38356
|
+
messageChains.delete(client.id);
|
|
38108
38357
|
cleanupClientSubs(client.id);
|
|
38109
38358
|
config.onWsClose?.(client.id);
|
|
38110
38359
|
connectionManager.removeClient(client.id);
|
|
@@ -38127,8 +38376,10 @@ async function createArcServer(config) {
|
|
|
38127
38376
|
}
|
|
38128
38377
|
// src/platform/server.ts
|
|
38129
38378
|
init_i18n();
|
|
38130
|
-
import {
|
|
38131
|
-
import {
|
|
38379
|
+
import { timingSafeEqual } from "crypto";
|
|
38380
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync15 } from "fs";
|
|
38381
|
+
import { join as join22 } from "path";
|
|
38382
|
+
import { MAP_SCHEMA_VERSION } from "@arcote.tech/arc-map/types";
|
|
38132
38383
|
async function resolveDbAdapterFactory(dbPath) {
|
|
38133
38384
|
const databaseUrl = process.env.DATABASE_URL;
|
|
38134
38385
|
if (databaseUrl && databaseUrl.length > 0) {
|
|
@@ -38138,10 +38389,10 @@ async function resolveDbAdapterFactory(dbPath) {
|
|
|
38138
38389
|
const { createBunSQLiteAdapterFactory: createBunSQLiteAdapterFactory2 } = await Promise.resolve().then(() => (init_dist2(), exports_dist3));
|
|
38139
38390
|
const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
38140
38391
|
if (dbDir)
|
|
38141
|
-
|
|
38392
|
+
mkdirSync15(dbDir, { recursive: true });
|
|
38142
38393
|
return createBunSQLiteAdapterFactory2(dbPath);
|
|
38143
38394
|
}
|
|
38144
|
-
function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
38395
|
+
function generateShellHtml(appName, manifest, initial, stylesHash, shell) {
|
|
38145
38396
|
const otelConfig = process.env.ARC_OTEL_ENABLED === "true" ? {
|
|
38146
38397
|
enabled: true,
|
|
38147
38398
|
endpoint: "/otel",
|
|
@@ -38155,6 +38406,9 @@ function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
|
38155
38406
|
if (!initialUrl) {
|
|
38156
38407
|
throw new Error("generateShellHtml: initial bundle missing from manifest");
|
|
38157
38408
|
}
|
|
38409
|
+
const importMapTag = shell ? `
|
|
38410
|
+
<script type="importmap">${JSON.stringify({ imports: shell.imports })}</script>
|
|
38411
|
+
<script>window.__ARC_PEERS__=${JSON.stringify(shell.peers)};</script>` : "";
|
|
38158
38412
|
const stylesQs = stylesHash ? `?v=${stylesHash.slice(0, 16)}` : "";
|
|
38159
38413
|
return `<!doctype html>
|
|
38160
38414
|
<html lang="en">
|
|
@@ -38165,7 +38419,7 @@ function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
|
38165
38419
|
<link rel="icon" href="${manifest.favicon}">` : ""}${manifest ? `
|
|
38166
38420
|
<link rel="manifest" href="/manifest.json${stylesQs}">` : ""}
|
|
38167
38421
|
<link rel="stylesheet" href="/styles.css${stylesQs}" />
|
|
38168
|
-
<link rel="stylesheet" href="/theme.css${stylesQs}"
|
|
38422
|
+
<link rel="stylesheet" href="/theme.css${stylesQs}" />${importMapTag}
|
|
38169
38423
|
<link rel="modulepreload" href="${initialUrl}" />${otelTag}
|
|
38170
38424
|
</head>
|
|
38171
38425
|
<body>
|
|
@@ -38197,7 +38451,7 @@ function getMime(path4) {
|
|
|
38197
38451
|
return MIME[ext2] ?? "application/octet-stream";
|
|
38198
38452
|
}
|
|
38199
38453
|
function serveFile(filePath, headers = {}) {
|
|
38200
|
-
if (!
|
|
38454
|
+
if (!existsSync19(filePath))
|
|
38201
38455
|
return new Response("Not Found", { status: 404 });
|
|
38202
38456
|
return new Response(Bun.file(filePath), {
|
|
38203
38457
|
headers: { "Content-Type": getMime(filePath), ...headers }
|
|
@@ -38215,11 +38469,44 @@ function ensureModuleSigSecret(ws, devMode) {
|
|
|
38215
38469
|
MODULE_SIG_SECRET = crypto.randomUUID();
|
|
38216
38470
|
}
|
|
38217
38471
|
}
|
|
38218
|
-
function signGroupUrl(file) {
|
|
38472
|
+
function signGroupUrl(file, prefix = "/browser") {
|
|
38219
38473
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
38220
38474
|
hasher.update(`${file}:${MODULE_SIG_SECRET}`);
|
|
38221
38475
|
const sig = hasher.digest("hex").slice(0, 16);
|
|
38222
|
-
return
|
|
38476
|
+
return `${prefix}/${file}?sig=${sig}`;
|
|
38477
|
+
}
|
|
38478
|
+
async function filterGroupsForTokens(groups, moduleAccessMap, tokenPayloads, urlPrefix) {
|
|
38479
|
+
const tokensByName = new Map;
|
|
38480
|
+
for (const t of tokenPayloads) {
|
|
38481
|
+
if (t?.tokenType)
|
|
38482
|
+
tokensByName.set(t.tokenType, t);
|
|
38483
|
+
}
|
|
38484
|
+
const filtered = {};
|
|
38485
|
+
for (const [name, group] of Object.entries(groups)) {
|
|
38486
|
+
let allGranted = true;
|
|
38487
|
+
for (const moduleName of group.modules) {
|
|
38488
|
+
const access = moduleAccessMap.get(moduleName);
|
|
38489
|
+
if (!access || access.rules.length === 0)
|
|
38490
|
+
continue;
|
|
38491
|
+
let granted = false;
|
|
38492
|
+
for (const rule of access.rules) {
|
|
38493
|
+
const matching = tokensByName.get(rule.token.name);
|
|
38494
|
+
if (!matching)
|
|
38495
|
+
continue;
|
|
38496
|
+
granted = rule.check ? await rule.check(matching) : true;
|
|
38497
|
+
if (granted)
|
|
38498
|
+
break;
|
|
38499
|
+
}
|
|
38500
|
+
if (!granted) {
|
|
38501
|
+
allGranted = false;
|
|
38502
|
+
break;
|
|
38503
|
+
}
|
|
38504
|
+
}
|
|
38505
|
+
if (allGranted) {
|
|
38506
|
+
filtered[name] = { ...group, url: signGroupUrl(group.file, urlPrefix) };
|
|
38507
|
+
}
|
|
38508
|
+
}
|
|
38509
|
+
return filtered;
|
|
38223
38510
|
}
|
|
38224
38511
|
function verifyGroupSignature(file, sig) {
|
|
38225
38512
|
if (!sig)
|
|
@@ -38260,36 +38547,7 @@ function parseArcTokensHeader(header) {
|
|
|
38260
38547
|
return payloads;
|
|
38261
38548
|
}
|
|
38262
38549
|
async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
|
|
38263
|
-
const
|
|
38264
|
-
for (const t of tokenPayloads) {
|
|
38265
|
-
if (t?.tokenType)
|
|
38266
|
-
tokensByName.set(t.tokenType, t);
|
|
38267
|
-
}
|
|
38268
|
-
const filteredGroups = {};
|
|
38269
|
-
for (const [name, group] of Object.entries(manifest.groups)) {
|
|
38270
|
-
let allGranted = true;
|
|
38271
|
-
for (const moduleName of group.modules) {
|
|
38272
|
-
const access = moduleAccessMap.get(moduleName);
|
|
38273
|
-
if (!access || access.rules.length === 0)
|
|
38274
|
-
continue;
|
|
38275
|
-
let granted = false;
|
|
38276
|
-
for (const rule of access.rules) {
|
|
38277
|
-
const matching = tokensByName.get(rule.token.name);
|
|
38278
|
-
if (!matching)
|
|
38279
|
-
continue;
|
|
38280
|
-
granted = rule.check ? await rule.check(matching) : true;
|
|
38281
|
-
if (granted)
|
|
38282
|
-
break;
|
|
38283
|
-
}
|
|
38284
|
-
if (!granted) {
|
|
38285
|
-
allGranted = false;
|
|
38286
|
-
break;
|
|
38287
|
-
}
|
|
38288
|
-
}
|
|
38289
|
-
if (allGranted) {
|
|
38290
|
-
filteredGroups[name] = { ...group, url: signGroupUrl(group.file) };
|
|
38291
|
-
}
|
|
38292
|
-
}
|
|
38550
|
+
const filteredGroups = await filterGroupsForTokens(manifest.groups, moduleAccessMap, tokenPayloads, "/browser");
|
|
38293
38551
|
return {
|
|
38294
38552
|
initial: manifest.initial,
|
|
38295
38553
|
groups: filteredGroups,
|
|
@@ -38315,28 +38573,58 @@ function staticFilesHandler(ws, devMode, getManifest) {
|
|
|
38315
38573
|
return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
|
|
38316
38574
|
}
|
|
38317
38575
|
}
|
|
38318
|
-
return serveFile(
|
|
38576
|
+
return serveFile(join22(ws.browserDir, file), {
|
|
38577
|
+
...ctx.corsHeaders,
|
|
38578
|
+
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38579
|
+
});
|
|
38580
|
+
}
|
|
38581
|
+
if (path4.startsWith("/shell/")) {
|
|
38582
|
+
const file = path4.slice(7);
|
|
38583
|
+
if (!BROWSER_FILE_RE.test(file)) {
|
|
38584
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38585
|
+
}
|
|
38586
|
+
return serveFile(join22(ws.arcDir, "shell", file), {
|
|
38319
38587
|
...ctx.corsHeaders,
|
|
38588
|
+
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
38589
|
+
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38590
|
+
});
|
|
38591
|
+
}
|
|
38592
|
+
if (path4.startsWith("/browser-fed/")) {
|
|
38593
|
+
const file = path4.slice(13);
|
|
38594
|
+
if (!BROWSER_FILE_RE.test(file)) {
|
|
38595
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38596
|
+
}
|
|
38597
|
+
const fed = getManifest().federation;
|
|
38598
|
+
const isGroupEntry = fed ? Object.values(fed.build.groups).some((g3) => g3.file === file) : false;
|
|
38599
|
+
if (isGroupEntry) {
|
|
38600
|
+
const sig = url.searchParams.get("sig");
|
|
38601
|
+
if (!verifyGroupSignature(file, sig)) {
|
|
38602
|
+
return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
|
|
38603
|
+
}
|
|
38604
|
+
}
|
|
38605
|
+
return serveFile(join22(ws.arcDir, "browser-fed", file), {
|
|
38606
|
+
...ctx.corsHeaders,
|
|
38607
|
+
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
38320
38608
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38321
38609
|
});
|
|
38322
38610
|
}
|
|
38323
38611
|
if (path4.startsWith("/locales/"))
|
|
38324
|
-
return serveFile(
|
|
38612
|
+
return serveFile(join22(ws.arcDir, path4.slice(1)), {
|
|
38325
38613
|
...ctx.corsHeaders,
|
|
38326
38614
|
"Cache-Control": devMode ? "no-cache" : "max-age=300,stale-while-revalidate=3600"
|
|
38327
38615
|
});
|
|
38328
38616
|
if (path4.startsWith("/assets/"))
|
|
38329
|
-
return serveFile(
|
|
38617
|
+
return serveFile(join22(ws.assetsDir, path4.slice(8)), {
|
|
38330
38618
|
...ctx.corsHeaders,
|
|
38331
38619
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38332
38620
|
});
|
|
38333
38621
|
if (path4 === "/styles.css")
|
|
38334
|
-
return serveFile(
|
|
38622
|
+
return serveFile(join22(ws.arcDir, "styles.css"), {
|
|
38335
38623
|
...ctx.corsHeaders,
|
|
38336
38624
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38337
38625
|
});
|
|
38338
38626
|
if (path4 === "/theme.css")
|
|
38339
|
-
return serveFile(
|
|
38627
|
+
return serveFile(join22(ws.arcDir, "theme.css"), {
|
|
38340
38628
|
...ctx.corsHeaders,
|
|
38341
38629
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38342
38630
|
});
|
|
@@ -38347,15 +38635,113 @@ function staticFilesHandler(ws, devMode, getManifest) {
|
|
|
38347
38635
|
});
|
|
38348
38636
|
}
|
|
38349
38637
|
if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
|
|
38350
|
-
const publicFile =
|
|
38351
|
-
if (
|
|
38638
|
+
const publicFile = join22(ws.publicDir, path4.slice(1));
|
|
38639
|
+
if (existsSync19(publicFile))
|
|
38352
38640
|
return serveFile(publicFile, ctx.corsHeaders);
|
|
38353
38641
|
}
|
|
38354
38642
|
return null;
|
|
38355
38643
|
};
|
|
38356
38644
|
}
|
|
38357
|
-
function
|
|
38645
|
+
function parseCorsOrigins(raw) {
|
|
38646
|
+
if (!raw)
|
|
38647
|
+
return;
|
|
38648
|
+
const list = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
38649
|
+
return list.length > 0 ? list : undefined;
|
|
38650
|
+
}
|
|
38651
|
+
function safeEqual(a2, b4) {
|
|
38652
|
+
const ab = Buffer.from(a2);
|
|
38653
|
+
const bb = Buffer.from(b4);
|
|
38654
|
+
if (ab.length !== bb.length)
|
|
38655
|
+
return false;
|
|
38656
|
+
return timingSafeEqual(ab, bb);
|
|
38657
|
+
}
|
|
38658
|
+
function handleDiscovery(req, ctx, ws, manifest, federation, mapUrl) {
|
|
38659
|
+
if (!federation) {
|
|
38660
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38661
|
+
}
|
|
38662
|
+
const presented = req.headers.get("X-Arc-Pairing-Token");
|
|
38663
|
+
if (!presented || !safeEqual(presented, federation.registrationToken)) {
|
|
38664
|
+
return new Response("Unauthorized", {
|
|
38665
|
+
status: 401,
|
|
38666
|
+
headers: ctx.corsHeaders
|
|
38667
|
+
});
|
|
38668
|
+
}
|
|
38669
|
+
const fed = manifest.federation;
|
|
38670
|
+
const platformUrl = process.env.ARC_PUBLIC_URL || new URL(req.url).origin;
|
|
38671
|
+
return Response.json({
|
|
38672
|
+
meta: {
|
|
38673
|
+
schemaVersion: MAP_SCHEMA_VERSION,
|
|
38674
|
+
name: ws.appName,
|
|
38675
|
+
version: ws.rootPkg?.version ?? undefined
|
|
38676
|
+
},
|
|
38677
|
+
platformUrl,
|
|
38678
|
+
federation: fed ? {
|
|
38679
|
+
slug: fed.config.slug,
|
|
38680
|
+
auth: fed.config.auth,
|
|
38681
|
+
modules: fed.modules,
|
|
38682
|
+
permissions: fed.permissions,
|
|
38683
|
+
slots: fed.slots
|
|
38684
|
+
} : null,
|
|
38685
|
+
peers: manifest.shell?.peers ?? null,
|
|
38686
|
+
federationManifestUrl: fed ? `${platformUrl}/api/federation/manifest` : null,
|
|
38687
|
+
mapUrl: mapUrl ?? null
|
|
38688
|
+
}, { headers: { ...ctx.corsHeaders, "Cache-Control": "no-cache, private" } });
|
|
38689
|
+
}
|
|
38690
|
+
async function handleFederationRegister(req, ctx, manifest, federation) {
|
|
38691
|
+
const fed = manifest.federation;
|
|
38692
|
+
if (!fed || !federation) {
|
|
38693
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38694
|
+
}
|
|
38695
|
+
const presented = req.headers.get("X-Arc-Pairing-Token");
|
|
38696
|
+
if (!presented || !safeEqual(presented, federation.registrationToken)) {
|
|
38697
|
+
return new Response("Unauthorized", {
|
|
38698
|
+
status: 401,
|
|
38699
|
+
headers: ctx.corsHeaders
|
|
38700
|
+
});
|
|
38701
|
+
}
|
|
38702
|
+
let body;
|
|
38703
|
+
try {
|
|
38704
|
+
body = await req.json();
|
|
38705
|
+
} catch {
|
|
38706
|
+
return new Response("Bad Request", {
|
|
38707
|
+
status: 400,
|
|
38708
|
+
headers: ctx.corsHeaders
|
|
38709
|
+
});
|
|
38710
|
+
}
|
|
38711
|
+
const hostId = typeof body.hostId === "string" ? body.hostId : "";
|
|
38712
|
+
const hostUrl = typeof body.hostUrl === "string" ? body.hostUrl : "";
|
|
38713
|
+
const hostName = typeof body.hostName === "string" ? body.hostName : hostId;
|
|
38714
|
+
const organizationId = typeof body.organizationId === "string" ? body.organizationId : "";
|
|
38715
|
+
if (!hostId || !hostUrl) {
|
|
38716
|
+
return new Response("Bad Request: hostId + hostUrl required", {
|
|
38717
|
+
status: 400,
|
|
38718
|
+
headers: ctx.corsHeaders
|
|
38719
|
+
});
|
|
38720
|
+
}
|
|
38721
|
+
ok(`Federation: host "${hostName}" (${hostUrl}) registered for org ${organizationId || "\u2014"}`);
|
|
38722
|
+
return Response.json({
|
|
38723
|
+
secret: federation.appSecret,
|
|
38724
|
+
slug: fed.config.slug,
|
|
38725
|
+
auth: fed.config.auth,
|
|
38726
|
+
modules: fed.modules,
|
|
38727
|
+
permissions: fed.permissions,
|
|
38728
|
+
slots: fed.slots
|
|
38729
|
+
}, { headers: { ...ctx.corsHeaders, "Cache-Control": "no-store" } });
|
|
38730
|
+
}
|
|
38731
|
+
function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap, telemetry, federation, getMapUrl) {
|
|
38358
38732
|
return (req, url, ctx) => {
|
|
38733
|
+
if (url.pathname === "/api/discovery") {
|
|
38734
|
+
return handleDiscovery(req, ctx, ws, getManifest(), federation, getMapUrl?.());
|
|
38735
|
+
}
|
|
38736
|
+
if (url.pathname === "/api/federation/register") {
|
|
38737
|
+
if (req.method !== "POST") {
|
|
38738
|
+
return new Response("Method Not Allowed", {
|
|
38739
|
+
status: 405,
|
|
38740
|
+
headers: { ...ctx.corsHeaders, Allow: "POST, OPTIONS" }
|
|
38741
|
+
});
|
|
38742
|
+
}
|
|
38743
|
+
return handleFederationRegister(req, ctx, getManifest(), federation);
|
|
38744
|
+
}
|
|
38359
38745
|
if (url.pathname === "/api/modules") {
|
|
38360
38746
|
const arcTokensHeader = req.headers.get("X-Arc-Tokens");
|
|
38361
38747
|
let payloads = parseArcTokensHeader(arcTokensHeader);
|
|
@@ -38369,6 +38755,36 @@ function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap, telemetry) {
|
|
|
38369
38755
|
}
|
|
38370
38756
|
}));
|
|
38371
38757
|
}
|
|
38758
|
+
if (url.pathname === "/api/federation/manifest") {
|
|
38759
|
+
const m4 = getManifest();
|
|
38760
|
+
if (!m4.federation || !m4.shell) {
|
|
38761
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
38762
|
+
}
|
|
38763
|
+
const arcTokensHeader = req.headers.get("X-Arc-Tokens");
|
|
38764
|
+
let payloads = parseArcTokensHeader(arcTokensHeader);
|
|
38765
|
+
if (payloads.length === 0 && ctx.tokenPayload) {
|
|
38766
|
+
payloads = [ctx.tokenPayload];
|
|
38767
|
+
}
|
|
38768
|
+
const fed = m4.federation;
|
|
38769
|
+
return filterGroupsForTokens(fed.build.groups, moduleAccessMap, payloads, "/browser-fed").then((groups) => Response.json({
|
|
38770
|
+
initial: fed.build.initial,
|
|
38771
|
+
groups,
|
|
38772
|
+
sharedChunks: fed.build.sharedChunks,
|
|
38773
|
+
stylesHash: m4.stylesHash,
|
|
38774
|
+
peers: m4.shell.peers,
|
|
38775
|
+
app: {
|
|
38776
|
+
slug: fed.config.slug,
|
|
38777
|
+
name: ws.appName,
|
|
38778
|
+
version: ws.rootPkg.version ?? undefined,
|
|
38779
|
+
modules: fed.modules,
|
|
38780
|
+
permissions: fed.permissions,
|
|
38781
|
+
slots: fed.slots,
|
|
38782
|
+
auth: fed.config.auth
|
|
38783
|
+
}
|
|
38784
|
+
}, {
|
|
38785
|
+
headers: { ...ctx.corsHeaders, "Cache-Control": "no-cache, private" }
|
|
38786
|
+
}));
|
|
38787
|
+
}
|
|
38372
38788
|
if (url.pathname === "/api/translations") {
|
|
38373
38789
|
const config = readTranslationsConfig(ws.rootDir);
|
|
38374
38790
|
return Response.json(config ?? { locales: [], sourceLocale: "" }, {
|
|
@@ -38414,6 +38830,17 @@ function devReloadHandler(sseClients) {
|
|
|
38414
38830
|
});
|
|
38415
38831
|
};
|
|
38416
38832
|
}
|
|
38833
|
+
function headlessFallbackHandler(ws, getManifest) {
|
|
38834
|
+
return (_req, _url, ctx) => {
|
|
38835
|
+
const m4 = getManifest();
|
|
38836
|
+
return Response.json({
|
|
38837
|
+
arc: "headless",
|
|
38838
|
+
app: ws.appName,
|
|
38839
|
+
federation: m4.federation ?? null,
|
|
38840
|
+
endpoints: ["/api/federation/manifest", "/api/modules", "/health"]
|
|
38841
|
+
}, { headers: ctx.corsHeaders });
|
|
38842
|
+
};
|
|
38843
|
+
}
|
|
38417
38844
|
function spaFallbackHandler(getShellHtml) {
|
|
38418
38845
|
return (_req, _url, ctx) => {
|
|
38419
38846
|
return new Response(getShellHtml(), {
|
|
@@ -38456,8 +38883,14 @@ async function startPlatformServer(opts) {
|
|
|
38456
38883
|
const setManifest = (m4) => {
|
|
38457
38884
|
manifest = m4;
|
|
38458
38885
|
};
|
|
38459
|
-
|
|
38886
|
+
let mapUrl = opts.mapUrl;
|
|
38887
|
+
const setMapUrl = (u2) => {
|
|
38888
|
+
mapUrl = u2;
|
|
38889
|
+
};
|
|
38890
|
+
const getMapUrl = () => mapUrl;
|
|
38891
|
+
const getShellHtml = () => generateShellHtml(ws.appName, ws.manifest, manifest?.initial, manifest?.stylesHash, manifest?.shell);
|
|
38460
38892
|
const sseClients = new Set;
|
|
38893
|
+
const fallbackHandler = opts.headless ? headlessFallbackHandler(ws, getManifest) : spaFallbackHandler(getShellHtml);
|
|
38461
38894
|
const notifyReload = (m4) => {
|
|
38462
38895
|
const data = JSON.stringify(m4);
|
|
38463
38896
|
for (const c2 of sseClients) {
|
|
@@ -38483,18 +38916,23 @@ async function startPlatformServer(opts) {
|
|
|
38483
38916
|
port,
|
|
38484
38917
|
async fetch(req) {
|
|
38485
38918
|
const url = new URL(req.url);
|
|
38486
|
-
if (req.method === "OPTIONS")
|
|
38487
|
-
|
|
38919
|
+
if (req.method === "OPTIONS") {
|
|
38920
|
+
const headers = { ...cors };
|
|
38921
|
+
if (req.headers.get("Access-Control-Request-Private-Network")) {
|
|
38922
|
+
headers["Access-Control-Allow-Private-Network"] = "true";
|
|
38923
|
+
}
|
|
38924
|
+
return new Response(null, { headers });
|
|
38925
|
+
}
|
|
38488
38926
|
const ctx = {
|
|
38489
38927
|
rawToken: null,
|
|
38490
38928
|
tokenPayload: null,
|
|
38491
38929
|
corsHeaders: cors
|
|
38492
38930
|
};
|
|
38493
38931
|
const handlers = [
|
|
38494
|
-
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
|
|
38932
|
+
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry, opts.federation, getMapUrl),
|
|
38495
38933
|
devReloadHandler(sseClients),
|
|
38496
38934
|
staticFilesHandler(ws, !!devMode, getManifest),
|
|
38497
|
-
|
|
38935
|
+
fallbackHandler
|
|
38498
38936
|
];
|
|
38499
38937
|
for (const handler of handlers) {
|
|
38500
38938
|
const response = await handler(req, url, ctx);
|
|
@@ -38509,11 +38947,12 @@ async function startPlatformServer(opts) {
|
|
|
38509
38947
|
contextHandler: null,
|
|
38510
38948
|
connectionManager: null,
|
|
38511
38949
|
setManifest,
|
|
38950
|
+
setMapUrl,
|
|
38512
38951
|
notifyReload,
|
|
38513
38952
|
stop: () => server.stop()
|
|
38514
38953
|
};
|
|
38515
38954
|
}
|
|
38516
|
-
const dbPath = opts.dbPath ||
|
|
38955
|
+
const dbPath = opts.dbPath || join22(ws.arcDir, "data", "arc.db");
|
|
38517
38956
|
const baseDbFactory = await resolveDbAdapterFactory(dbPath);
|
|
38518
38957
|
let dbAdapterFactory = baseDbFactory;
|
|
38519
38958
|
if (telemetry) {
|
|
@@ -38529,11 +38968,13 @@ async function startPlatformServer(opts) {
|
|
|
38529
38968
|
dbAdapterFactory,
|
|
38530
38969
|
port,
|
|
38531
38970
|
coep,
|
|
38971
|
+
corsOrigins: parseCorsOrigins(process.env.ARC_CORS_ORIGINS),
|
|
38972
|
+
allowPrivateNetwork: !!devMode,
|
|
38532
38973
|
httpHandlers: [
|
|
38533
|
-
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
|
|
38974
|
+
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry, opts.federation, getMapUrl),
|
|
38534
38975
|
devReloadHandler(sseClients),
|
|
38535
38976
|
staticFilesHandler(ws, !!devMode, getManifest),
|
|
38536
|
-
|
|
38977
|
+
fallbackHandler
|
|
38537
38978
|
],
|
|
38538
38979
|
onWsClose: (clientId) => cleanupClientSubs(clientId),
|
|
38539
38980
|
telemetry
|
|
@@ -38543,6 +38984,7 @@ async function startPlatformServer(opts) {
|
|
|
38543
38984
|
contextHandler: arcServer.contextHandler,
|
|
38544
38985
|
connectionManager: arcServer.connectionManager,
|
|
38545
38986
|
setManifest,
|
|
38987
|
+
setMapUrl,
|
|
38546
38988
|
notifyReload,
|
|
38547
38989
|
stop: () => {
|
|
38548
38990
|
arcServer.stop();
|
|
@@ -38555,17 +38997,23 @@ async function startPlatformServer(opts) {
|
|
|
38555
38997
|
async function startPlatform(opts) {
|
|
38556
38998
|
const { ws, devMode } = opts;
|
|
38557
38999
|
const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
|
|
38558
|
-
const dbPath = opts.dbPath ??
|
|
39000
|
+
const dbPath = opts.dbPath ?? join23(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
|
|
38559
39001
|
let manifest;
|
|
38560
39002
|
if (devMode) {
|
|
38561
39003
|
manifest = await buildAll(ws);
|
|
38562
39004
|
} else {
|
|
38563
|
-
const manifestPath =
|
|
38564
|
-
if (!
|
|
39005
|
+
const manifestPath = join23(ws.arcDir, "manifest.json");
|
|
39006
|
+
if (!existsSync20(manifestPath)) {
|
|
38565
39007
|
err("No build found. Run `arc platform build` first.");
|
|
38566
39008
|
process.exit(1);
|
|
38567
39009
|
}
|
|
38568
|
-
manifest = JSON.parse(
|
|
39010
|
+
manifest = JSON.parse(readFileSync17(manifestPath, "utf-8"));
|
|
39011
|
+
}
|
|
39012
|
+
assertProdFederationSecrets(devMode, !!manifest.federation);
|
|
39013
|
+
const needsPairing = !!manifest.federation || devMode && (opts.map || opts.headless);
|
|
39014
|
+
const pairingToken = needsPairing ? resolvePairingToken(ws.rootDir) : undefined;
|
|
39015
|
+
if (manifest.federation) {
|
|
39016
|
+
process.env.ARC_APP_SECRET = resolveAppSecret(ws.rootDir);
|
|
38569
39017
|
}
|
|
38570
39018
|
log2("Loading server context...");
|
|
38571
39019
|
const { context: context2, moduleAccess } = await loadServerContext(ws);
|
|
@@ -38586,7 +39034,12 @@ async function startPlatform(opts) {
|
|
|
38586
39034
|
context: context2,
|
|
38587
39035
|
moduleAccess,
|
|
38588
39036
|
dbPath,
|
|
38589
|
-
devMode
|
|
39037
|
+
devMode,
|
|
39038
|
+
headless: opts.headless,
|
|
39039
|
+
federation: manifest.federation && pairingToken ? {
|
|
39040
|
+
registrationToken: pairingToken,
|
|
39041
|
+
appSecret: process.env.ARC_APP_SECRET
|
|
39042
|
+
} : undefined
|
|
38590
39043
|
});
|
|
38591
39044
|
break;
|
|
38592
39045
|
} catch (e2) {
|
|
@@ -38607,8 +39060,18 @@ async function startPlatform(opts) {
|
|
|
38607
39060
|
ok("Commands, queries, WebSocket \u2014 all on same port");
|
|
38608
39061
|
}
|
|
38609
39062
|
let onReload;
|
|
38610
|
-
if (devMode && opts.map) {
|
|
38611
|
-
onReload = await startMapTool(ws, actualPort + 1
|
|
39063
|
+
if (devMode && (opts.map || opts.headless)) {
|
|
39064
|
+
onReload = await startMapTool(ws, actualPort + 1, pairingToken, platform3, {
|
|
39065
|
+
platformUrl: `http://localhost:${actualPort}`,
|
|
39066
|
+
federation: manifest.federation ? {
|
|
39067
|
+
slug: manifest.federation.config.slug,
|
|
39068
|
+
auth: manifest.federation.config.auth,
|
|
39069
|
+
modules: manifest.federation.modules,
|
|
39070
|
+
permissions: manifest.federation.permissions,
|
|
39071
|
+
slots: manifest.federation.slots
|
|
39072
|
+
} : undefined,
|
|
39073
|
+
peers: manifest.shell?.peers
|
|
39074
|
+
});
|
|
38612
39075
|
}
|
|
38613
39076
|
if (devMode) {
|
|
38614
39077
|
attachDevWatcher(ws, platform3, onReload);
|
|
@@ -38620,25 +39083,98 @@ async function startPlatform(opts) {
|
|
|
38620
39083
|
process.on("SIGTERM", cleanup);
|
|
38621
39084
|
process.on("SIGINT", cleanup);
|
|
38622
39085
|
}
|
|
38623
|
-
async function startMapTool(ws, port) {
|
|
39086
|
+
async function startMapTool(ws, port, authToken, platformServer, platform3) {
|
|
38624
39087
|
try {
|
|
38625
|
-
const { startMapServer } = await import("@arcote.tech/arc-map");
|
|
39088
|
+
const { startMapServer, MAP_SCHEMA_VERSION: MAP_SCHEMA_VERSION2 } = await import("@arcote.tech/arc-map");
|
|
38626
39089
|
const { getContext } = await import("@arcote.tech/platform");
|
|
38627
|
-
const globs = ws.packages.map((p3) =>
|
|
38628
|
-
const packageOf = (absFile) => ws.packages.find((p3) => absFile.startsWith(
|
|
39090
|
+
const globs = ws.packages.map((p3) => join23(p3.path, "src", "**", "*.{ts,tsx}"));
|
|
39091
|
+
const packageOf = (absFile) => ws.packages.find((p3) => absFile.startsWith(join23(p3.path, "src")))?.name;
|
|
39092
|
+
const meta = readAppMeta(ws.rootDir, MAP_SCHEMA_VERSION2);
|
|
38629
39093
|
const map = await startMapServer({
|
|
38630
39094
|
getContext: () => getContext(),
|
|
38631
39095
|
scan: { globs, workspaceRoot: ws.rootDir, packageOf },
|
|
38632
39096
|
useCasePackages: ws.packages.map((p3) => ({ name: p3.name, path: p3.path })),
|
|
38633
|
-
port
|
|
39097
|
+
port,
|
|
39098
|
+
authToken,
|
|
39099
|
+
meta,
|
|
39100
|
+
platform: platform3
|
|
38634
39101
|
});
|
|
39102
|
+
platformServer.setMapUrl(map.url);
|
|
38635
39103
|
ok(`Context map \u2192 ${map.url}`);
|
|
39104
|
+
ok(`Pairing token: ${authToken}`);
|
|
38636
39105
|
return map.notifyReload;
|
|
38637
39106
|
} catch (e2) {
|
|
38638
39107
|
err(`Context map failed to start: ${e2.message}`);
|
|
38639
39108
|
return;
|
|
38640
39109
|
}
|
|
38641
39110
|
}
|
|
39111
|
+
function assertProdFederationSecrets(devMode, hasFederation) {
|
|
39112
|
+
if (devMode || !hasFederation)
|
|
39113
|
+
return;
|
|
39114
|
+
const missing = [];
|
|
39115
|
+
if (!process.env.ARC_APP_SECRET)
|
|
39116
|
+
missing.push("ARC_APP_SECRET");
|
|
39117
|
+
if (!process.env.ARC_PAIRING_TOKEN && !process.env.ARC_MAP_TOKEN) {
|
|
39118
|
+
missing.push("ARC_PAIRING_TOKEN");
|
|
39119
|
+
}
|
|
39120
|
+
if (missing.length === 0)
|
|
39121
|
+
return;
|
|
39122
|
+
err(`Federacja w produkcji wymaga zmiennych \u015Brodowiskowych: ${missing.join(", ")}.
|
|
39123
|
+
` + ` Bez nich sekret i token s\u0105 generowane w kontenerze i GIN\u0104 przy pierwszym
|
|
39124
|
+
` + ` recreate \u2014 hosty musia\u0142yby przeinstalowa\u0107 aplikacj\u0119, bez \u017Cadnego b\u0142\u0119du.
|
|
39125
|
+
` + ` Ustaw je w deployu (deploy.arc.json \u2192 envVars + deploy.arc.<env>.env).`);
|
|
39126
|
+
process.exit(1);
|
|
39127
|
+
}
|
|
39128
|
+
function resolveAppSecret(rootDir) {
|
|
39129
|
+
const fromEnv = process.env.ARC_APP_SECRET;
|
|
39130
|
+
if (fromEnv)
|
|
39131
|
+
return fromEnv;
|
|
39132
|
+
const file = join23(rootDir, ".arc", "app-secret");
|
|
39133
|
+
try {
|
|
39134
|
+
const saved = readFileSync17(file, "utf-8").trim();
|
|
39135
|
+
if (saved)
|
|
39136
|
+
return saved;
|
|
39137
|
+
} catch {}
|
|
39138
|
+
const secret = randomBytes2(32).toString("base64url");
|
|
39139
|
+
try {
|
|
39140
|
+
mkdirSync16(join23(rootDir, ".arc"), { recursive: true });
|
|
39141
|
+
writeFileSync16(file, secret, { mode: 384 });
|
|
39142
|
+
} catch (e2) {
|
|
39143
|
+
err(`App secret not persisted (${e2.message}) \u2014 hosts will need re-install after restart`);
|
|
39144
|
+
}
|
|
39145
|
+
return secret;
|
|
39146
|
+
}
|
|
39147
|
+
function resolvePairingToken(rootDir) {
|
|
39148
|
+
const fromEnv = process.env.ARC_PAIRING_TOKEN || process.env.ARC_MAP_TOKEN;
|
|
39149
|
+
if (fromEnv)
|
|
39150
|
+
return fromEnv;
|
|
39151
|
+
const file = join23(rootDir, ".arc", "pairing-token");
|
|
39152
|
+
try {
|
|
39153
|
+
const saved = readFileSync17(file, "utf-8").trim();
|
|
39154
|
+
if (saved)
|
|
39155
|
+
return saved;
|
|
39156
|
+
} catch {}
|
|
39157
|
+
const token = randomBytes2(16).toString("base64url");
|
|
39158
|
+
try {
|
|
39159
|
+
mkdirSync16(join23(rootDir, ".arc"), { recursive: true });
|
|
39160
|
+
writeFileSync16(file, token, { mode: 384 });
|
|
39161
|
+
} catch (e2) {
|
|
39162
|
+
err(`Pairing token not persisted (${e2.message}) \u2014 it will change on restart`);
|
|
39163
|
+
}
|
|
39164
|
+
return token;
|
|
39165
|
+
}
|
|
39166
|
+
function readAppMeta(rootDir, schemaVersion) {
|
|
39167
|
+
try {
|
|
39168
|
+
const pkg = JSON.parse(readFileSync17(join23(rootDir, "package.json"), "utf-8"));
|
|
39169
|
+
return {
|
|
39170
|
+
schemaVersion,
|
|
39171
|
+
name: pkg.name ?? rootDir.split("/").pop() ?? "arc-app",
|
|
39172
|
+
version: pkg.version
|
|
39173
|
+
};
|
|
39174
|
+
} catch {
|
|
39175
|
+
return { schemaVersion, name: rootDir.split("/").pop() ?? "arc-app" };
|
|
39176
|
+
}
|
|
39177
|
+
}
|
|
38642
39178
|
function attachDevWatcher(ws, platform3, onReload) {
|
|
38643
39179
|
log2("Watching for changes...");
|
|
38644
39180
|
let rebuildTimer = null;
|
|
@@ -38665,8 +39201,8 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38665
39201
|
}, 300);
|
|
38666
39202
|
};
|
|
38667
39203
|
for (const pkg of ws.packages) {
|
|
38668
|
-
const srcDir =
|
|
38669
|
-
if (!
|
|
39204
|
+
const srcDir = join23(pkg.path, "src");
|
|
39205
|
+
if (!existsSync20(srcDir))
|
|
38670
39206
|
continue;
|
|
38671
39207
|
watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
38672
39208
|
if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
|
|
@@ -38676,8 +39212,8 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38676
39212
|
triggerRebuild();
|
|
38677
39213
|
});
|
|
38678
39214
|
}
|
|
38679
|
-
const localesDir =
|
|
38680
|
-
if (
|
|
39215
|
+
const localesDir = join23(ws.rootDir, "locales");
|
|
39216
|
+
if (existsSync20(localesDir)) {
|
|
38681
39217
|
watch(localesDir, { recursive: false }, (_event, filename) => {
|
|
38682
39218
|
if (!filename?.endsWith(".po"))
|
|
38683
39219
|
return;
|
|
@@ -38689,7 +39225,12 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38689
39225
|
// src/commands/platform-dev.ts
|
|
38690
39226
|
async function platformDev(opts = {}) {
|
|
38691
39227
|
const ws = resolveWorkspace();
|
|
38692
|
-
await startPlatform({
|
|
39228
|
+
await startPlatform({
|
|
39229
|
+
ws,
|
|
39230
|
+
devMode: true,
|
|
39231
|
+
map: opts.map,
|
|
39232
|
+
headless: opts.headless
|
|
39233
|
+
});
|
|
38693
39234
|
}
|
|
38694
39235
|
|
|
38695
39236
|
// src/commands/platform-start.ts
|
|
@@ -38704,7 +39245,11 @@ program2.name("arc").description("CLI tool for Arc framework").version("0.0.3");
|
|
|
38704
39245
|
program2.command("dev").description("Run development mode for Arc framework").action(dev);
|
|
38705
39246
|
program2.command("build").description("Build all clients and declarations").action(build);
|
|
38706
39247
|
var platform3 = program2.command("platform").description("Platform commands \u2014 run full stack (server + UI)");
|
|
38707
|
-
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").
|
|
39248
|
+
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({
|
|
39249
|
+
noCache: opts.cache === false,
|
|
39250
|
+
map: opts.map,
|
|
39251
|
+
headless: opts.headless
|
|
39252
|
+
}));
|
|
38708
39253
|
platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
|
|
38709
39254
|
platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
|
|
38710
39255
|
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));
|