@arcote.tech/arc-cli 0.7.32 → 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 +886 -351
- 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
|
@@ -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 {
|
|
@@ -37947,6 +38184,7 @@ async function createArcServer(config) {
|
|
|
37947
38184
|
const messageChains = new Map;
|
|
37948
38185
|
const coep = config.coep ?? "unsafe-none";
|
|
37949
38186
|
const corsOrigins = config.corsOrigins;
|
|
38187
|
+
const allowPrivateNetwork = config.allowPrivateNetwork ?? false;
|
|
37950
38188
|
const maxRequestBodySize = config.maxRequestBodySize ?? 10 * 1024 * 1024;
|
|
37951
38189
|
const securityHeaders = {
|
|
37952
38190
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -37958,7 +38196,7 @@ async function createArcServer(config) {
|
|
|
37958
38196
|
const origin = req?.headers.get("Origin") || null;
|
|
37959
38197
|
const headers = {
|
|
37960
38198
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
37961
|
-
"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",
|
|
37962
38200
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
37963
38201
|
"Cross-Origin-Embedder-Policy": coep,
|
|
37964
38202
|
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
@@ -37995,7 +38233,8 @@ async function createArcServer(config) {
|
|
|
37995
38233
|
const url = new URL(req.url);
|
|
37996
38234
|
const corsHeaders2 = buildCorsHeaders(req);
|
|
37997
38235
|
if (req.method === "OPTIONS") {
|
|
37998
|
-
|
|
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 } });
|
|
37999
38238
|
}
|
|
38000
38239
|
const handleRequest = async (span) => {
|
|
38001
38240
|
const contentLength = Number(req.headers.get("Content-Length") ?? 0);
|
|
@@ -38137,8 +38376,10 @@ async function createArcServer(config) {
|
|
|
38137
38376
|
}
|
|
38138
38377
|
// src/platform/server.ts
|
|
38139
38378
|
init_i18n();
|
|
38140
|
-
import {
|
|
38141
|
-
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";
|
|
38142
38383
|
async function resolveDbAdapterFactory(dbPath) {
|
|
38143
38384
|
const databaseUrl = process.env.DATABASE_URL;
|
|
38144
38385
|
if (databaseUrl && databaseUrl.length > 0) {
|
|
@@ -38148,10 +38389,10 @@ async function resolveDbAdapterFactory(dbPath) {
|
|
|
38148
38389
|
const { createBunSQLiteAdapterFactory: createBunSQLiteAdapterFactory2 } = await Promise.resolve().then(() => (init_dist2(), exports_dist3));
|
|
38149
38390
|
const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
38150
38391
|
if (dbDir)
|
|
38151
|
-
|
|
38392
|
+
mkdirSync15(dbDir, { recursive: true });
|
|
38152
38393
|
return createBunSQLiteAdapterFactory2(dbPath);
|
|
38153
38394
|
}
|
|
38154
|
-
function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
38395
|
+
function generateShellHtml(appName, manifest, initial, stylesHash, shell) {
|
|
38155
38396
|
const otelConfig = process.env.ARC_OTEL_ENABLED === "true" ? {
|
|
38156
38397
|
enabled: true,
|
|
38157
38398
|
endpoint: "/otel",
|
|
@@ -38165,6 +38406,9 @@ function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
|
38165
38406
|
if (!initialUrl) {
|
|
38166
38407
|
throw new Error("generateShellHtml: initial bundle missing from manifest");
|
|
38167
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>` : "";
|
|
38168
38412
|
const stylesQs = stylesHash ? `?v=${stylesHash.slice(0, 16)}` : "";
|
|
38169
38413
|
return `<!doctype html>
|
|
38170
38414
|
<html lang="en">
|
|
@@ -38175,7 +38419,7 @@ function generateShellHtml(appName, manifest, initial, stylesHash) {
|
|
|
38175
38419
|
<link rel="icon" href="${manifest.favicon}">` : ""}${manifest ? `
|
|
38176
38420
|
<link rel="manifest" href="/manifest.json${stylesQs}">` : ""}
|
|
38177
38421
|
<link rel="stylesheet" href="/styles.css${stylesQs}" />
|
|
38178
|
-
<link rel="stylesheet" href="/theme.css${stylesQs}"
|
|
38422
|
+
<link rel="stylesheet" href="/theme.css${stylesQs}" />${importMapTag}
|
|
38179
38423
|
<link rel="modulepreload" href="${initialUrl}" />${otelTag}
|
|
38180
38424
|
</head>
|
|
38181
38425
|
<body>
|
|
@@ -38207,7 +38451,7 @@ function getMime(path4) {
|
|
|
38207
38451
|
return MIME[ext2] ?? "application/octet-stream";
|
|
38208
38452
|
}
|
|
38209
38453
|
function serveFile(filePath, headers = {}) {
|
|
38210
|
-
if (!
|
|
38454
|
+
if (!existsSync19(filePath))
|
|
38211
38455
|
return new Response("Not Found", { status: 404 });
|
|
38212
38456
|
return new Response(Bun.file(filePath), {
|
|
38213
38457
|
headers: { "Content-Type": getMime(filePath), ...headers }
|
|
@@ -38225,11 +38469,44 @@ function ensureModuleSigSecret(ws, devMode) {
|
|
|
38225
38469
|
MODULE_SIG_SECRET = crypto.randomUUID();
|
|
38226
38470
|
}
|
|
38227
38471
|
}
|
|
38228
|
-
function signGroupUrl(file) {
|
|
38472
|
+
function signGroupUrl(file, prefix = "/browser") {
|
|
38229
38473
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
38230
38474
|
hasher.update(`${file}:${MODULE_SIG_SECRET}`);
|
|
38231
38475
|
const sig = hasher.digest("hex").slice(0, 16);
|
|
38232
|
-
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;
|
|
38233
38510
|
}
|
|
38234
38511
|
function verifyGroupSignature(file, sig) {
|
|
38235
38512
|
if (!sig)
|
|
@@ -38270,36 +38547,7 @@ function parseArcTokensHeader(header) {
|
|
|
38270
38547
|
return payloads;
|
|
38271
38548
|
}
|
|
38272
38549
|
async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
|
|
38273
|
-
const
|
|
38274
|
-
for (const t of tokenPayloads) {
|
|
38275
|
-
if (t?.tokenType)
|
|
38276
|
-
tokensByName.set(t.tokenType, t);
|
|
38277
|
-
}
|
|
38278
|
-
const filteredGroups = {};
|
|
38279
|
-
for (const [name, group] of Object.entries(manifest.groups)) {
|
|
38280
|
-
let allGranted = true;
|
|
38281
|
-
for (const moduleName of group.modules) {
|
|
38282
|
-
const access = moduleAccessMap.get(moduleName);
|
|
38283
|
-
if (!access || access.rules.length === 0)
|
|
38284
|
-
continue;
|
|
38285
|
-
let granted = false;
|
|
38286
|
-
for (const rule of access.rules) {
|
|
38287
|
-
const matching = tokensByName.get(rule.token.name);
|
|
38288
|
-
if (!matching)
|
|
38289
|
-
continue;
|
|
38290
|
-
granted = rule.check ? await rule.check(matching) : true;
|
|
38291
|
-
if (granted)
|
|
38292
|
-
break;
|
|
38293
|
-
}
|
|
38294
|
-
if (!granted) {
|
|
38295
|
-
allGranted = false;
|
|
38296
|
-
break;
|
|
38297
|
-
}
|
|
38298
|
-
}
|
|
38299
|
-
if (allGranted) {
|
|
38300
|
-
filteredGroups[name] = { ...group, url: signGroupUrl(group.file) };
|
|
38301
|
-
}
|
|
38302
|
-
}
|
|
38550
|
+
const filteredGroups = await filterGroupsForTokens(manifest.groups, moduleAccessMap, tokenPayloads, "/browser");
|
|
38303
38551
|
return {
|
|
38304
38552
|
initial: manifest.initial,
|
|
38305
38553
|
groups: filteredGroups,
|
|
@@ -38325,28 +38573,58 @@ function staticFilesHandler(ws, devMode, getManifest) {
|
|
|
38325
38573
|
return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
|
|
38326
38574
|
}
|
|
38327
38575
|
}
|
|
38328
|
-
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), {
|
|
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), {
|
|
38329
38606
|
...ctx.corsHeaders,
|
|
38607
|
+
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
38330
38608
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38331
38609
|
});
|
|
38332
38610
|
}
|
|
38333
38611
|
if (path4.startsWith("/locales/"))
|
|
38334
|
-
return serveFile(
|
|
38612
|
+
return serveFile(join22(ws.arcDir, path4.slice(1)), {
|
|
38335
38613
|
...ctx.corsHeaders,
|
|
38336
38614
|
"Cache-Control": devMode ? "no-cache" : "max-age=300,stale-while-revalidate=3600"
|
|
38337
38615
|
});
|
|
38338
38616
|
if (path4.startsWith("/assets/"))
|
|
38339
|
-
return serveFile(
|
|
38617
|
+
return serveFile(join22(ws.assetsDir, path4.slice(8)), {
|
|
38340
38618
|
...ctx.corsHeaders,
|
|
38341
38619
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38342
38620
|
});
|
|
38343
38621
|
if (path4 === "/styles.css")
|
|
38344
|
-
return serveFile(
|
|
38622
|
+
return serveFile(join22(ws.arcDir, "styles.css"), {
|
|
38345
38623
|
...ctx.corsHeaders,
|
|
38346
38624
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38347
38625
|
});
|
|
38348
38626
|
if (path4 === "/theme.css")
|
|
38349
|
-
return serveFile(
|
|
38627
|
+
return serveFile(join22(ws.arcDir, "theme.css"), {
|
|
38350
38628
|
...ctx.corsHeaders,
|
|
38351
38629
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
38352
38630
|
});
|
|
@@ -38357,15 +38635,113 @@ function staticFilesHandler(ws, devMode, getManifest) {
|
|
|
38357
38635
|
});
|
|
38358
38636
|
}
|
|
38359
38637
|
if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
|
|
38360
|
-
const publicFile =
|
|
38361
|
-
if (
|
|
38638
|
+
const publicFile = join22(ws.publicDir, path4.slice(1));
|
|
38639
|
+
if (existsSync19(publicFile))
|
|
38362
38640
|
return serveFile(publicFile, ctx.corsHeaders);
|
|
38363
38641
|
}
|
|
38364
38642
|
return null;
|
|
38365
38643
|
};
|
|
38366
38644
|
}
|
|
38367
|
-
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) {
|
|
38368
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
|
+
}
|
|
38369
38745
|
if (url.pathname === "/api/modules") {
|
|
38370
38746
|
const arcTokensHeader = req.headers.get("X-Arc-Tokens");
|
|
38371
38747
|
let payloads = parseArcTokensHeader(arcTokensHeader);
|
|
@@ -38379,6 +38755,36 @@ function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap, telemetry) {
|
|
|
38379
38755
|
}
|
|
38380
38756
|
}));
|
|
38381
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
|
+
}
|
|
38382
38788
|
if (url.pathname === "/api/translations") {
|
|
38383
38789
|
const config = readTranslationsConfig(ws.rootDir);
|
|
38384
38790
|
return Response.json(config ?? { locales: [], sourceLocale: "" }, {
|
|
@@ -38424,6 +38830,17 @@ function devReloadHandler(sseClients) {
|
|
|
38424
38830
|
});
|
|
38425
38831
|
};
|
|
38426
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
|
+
}
|
|
38427
38844
|
function spaFallbackHandler(getShellHtml) {
|
|
38428
38845
|
return (_req, _url, ctx) => {
|
|
38429
38846
|
return new Response(getShellHtml(), {
|
|
@@ -38466,8 +38883,14 @@ async function startPlatformServer(opts) {
|
|
|
38466
38883
|
const setManifest = (m4) => {
|
|
38467
38884
|
manifest = m4;
|
|
38468
38885
|
};
|
|
38469
|
-
|
|
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);
|
|
38470
38892
|
const sseClients = new Set;
|
|
38893
|
+
const fallbackHandler = opts.headless ? headlessFallbackHandler(ws, getManifest) : spaFallbackHandler(getShellHtml);
|
|
38471
38894
|
const notifyReload = (m4) => {
|
|
38472
38895
|
const data = JSON.stringify(m4);
|
|
38473
38896
|
for (const c2 of sseClients) {
|
|
@@ -38493,18 +38916,23 @@ async function startPlatformServer(opts) {
|
|
|
38493
38916
|
port,
|
|
38494
38917
|
async fetch(req) {
|
|
38495
38918
|
const url = new URL(req.url);
|
|
38496
|
-
if (req.method === "OPTIONS")
|
|
38497
|
-
|
|
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
|
+
}
|
|
38498
38926
|
const ctx = {
|
|
38499
38927
|
rawToken: null,
|
|
38500
38928
|
tokenPayload: null,
|
|
38501
38929
|
corsHeaders: cors
|
|
38502
38930
|
};
|
|
38503
38931
|
const handlers = [
|
|
38504
|
-
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
|
|
38932
|
+
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry, opts.federation, getMapUrl),
|
|
38505
38933
|
devReloadHandler(sseClients),
|
|
38506
38934
|
staticFilesHandler(ws, !!devMode, getManifest),
|
|
38507
|
-
|
|
38935
|
+
fallbackHandler
|
|
38508
38936
|
];
|
|
38509
38937
|
for (const handler of handlers) {
|
|
38510
38938
|
const response = await handler(req, url, ctx);
|
|
@@ -38519,11 +38947,12 @@ async function startPlatformServer(opts) {
|
|
|
38519
38947
|
contextHandler: null,
|
|
38520
38948
|
connectionManager: null,
|
|
38521
38949
|
setManifest,
|
|
38950
|
+
setMapUrl,
|
|
38522
38951
|
notifyReload,
|
|
38523
38952
|
stop: () => server.stop()
|
|
38524
38953
|
};
|
|
38525
38954
|
}
|
|
38526
|
-
const dbPath = opts.dbPath ||
|
|
38955
|
+
const dbPath = opts.dbPath || join22(ws.arcDir, "data", "arc.db");
|
|
38527
38956
|
const baseDbFactory = await resolveDbAdapterFactory(dbPath);
|
|
38528
38957
|
let dbAdapterFactory = baseDbFactory;
|
|
38529
38958
|
if (telemetry) {
|
|
@@ -38539,11 +38968,13 @@ async function startPlatformServer(opts) {
|
|
|
38539
38968
|
dbAdapterFactory,
|
|
38540
38969
|
port,
|
|
38541
38970
|
coep,
|
|
38971
|
+
corsOrigins: parseCorsOrigins(process.env.ARC_CORS_ORIGINS),
|
|
38972
|
+
allowPrivateNetwork: !!devMode,
|
|
38542
38973
|
httpHandlers: [
|
|
38543
|
-
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
|
|
38974
|
+
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry, opts.federation, getMapUrl),
|
|
38544
38975
|
devReloadHandler(sseClients),
|
|
38545
38976
|
staticFilesHandler(ws, !!devMode, getManifest),
|
|
38546
|
-
|
|
38977
|
+
fallbackHandler
|
|
38547
38978
|
],
|
|
38548
38979
|
onWsClose: (clientId) => cleanupClientSubs(clientId),
|
|
38549
38980
|
telemetry
|
|
@@ -38553,6 +38984,7 @@ async function startPlatformServer(opts) {
|
|
|
38553
38984
|
contextHandler: arcServer.contextHandler,
|
|
38554
38985
|
connectionManager: arcServer.connectionManager,
|
|
38555
38986
|
setManifest,
|
|
38987
|
+
setMapUrl,
|
|
38556
38988
|
notifyReload,
|
|
38557
38989
|
stop: () => {
|
|
38558
38990
|
arcServer.stop();
|
|
@@ -38565,17 +38997,23 @@ async function startPlatformServer(opts) {
|
|
|
38565
38997
|
async function startPlatform(opts) {
|
|
38566
38998
|
const { ws, devMode } = opts;
|
|
38567
38999
|
const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
|
|
38568
|
-
const dbPath = opts.dbPath ??
|
|
39000
|
+
const dbPath = opts.dbPath ?? join23(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
|
|
38569
39001
|
let manifest;
|
|
38570
39002
|
if (devMode) {
|
|
38571
39003
|
manifest = await buildAll(ws);
|
|
38572
39004
|
} else {
|
|
38573
|
-
const manifestPath =
|
|
38574
|
-
if (!
|
|
39005
|
+
const manifestPath = join23(ws.arcDir, "manifest.json");
|
|
39006
|
+
if (!existsSync20(manifestPath)) {
|
|
38575
39007
|
err("No build found. Run `arc platform build` first.");
|
|
38576
39008
|
process.exit(1);
|
|
38577
39009
|
}
|
|
38578
|
-
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);
|
|
38579
39017
|
}
|
|
38580
39018
|
log2("Loading server context...");
|
|
38581
39019
|
const { context: context2, moduleAccess } = await loadServerContext(ws);
|
|
@@ -38596,7 +39034,12 @@ async function startPlatform(opts) {
|
|
|
38596
39034
|
context: context2,
|
|
38597
39035
|
moduleAccess,
|
|
38598
39036
|
dbPath,
|
|
38599
|
-
devMode
|
|
39037
|
+
devMode,
|
|
39038
|
+
headless: opts.headless,
|
|
39039
|
+
federation: manifest.federation && pairingToken ? {
|
|
39040
|
+
registrationToken: pairingToken,
|
|
39041
|
+
appSecret: process.env.ARC_APP_SECRET
|
|
39042
|
+
} : undefined
|
|
38600
39043
|
});
|
|
38601
39044
|
break;
|
|
38602
39045
|
} catch (e2) {
|
|
@@ -38617,8 +39060,18 @@ async function startPlatform(opts) {
|
|
|
38617
39060
|
ok("Commands, queries, WebSocket \u2014 all on same port");
|
|
38618
39061
|
}
|
|
38619
39062
|
let onReload;
|
|
38620
|
-
if (devMode && opts.map) {
|
|
38621
|
-
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
|
+
});
|
|
38622
39075
|
}
|
|
38623
39076
|
if (devMode) {
|
|
38624
39077
|
attachDevWatcher(ws, platform3, onReload);
|
|
@@ -38630,25 +39083,98 @@ async function startPlatform(opts) {
|
|
|
38630
39083
|
process.on("SIGTERM", cleanup);
|
|
38631
39084
|
process.on("SIGINT", cleanup);
|
|
38632
39085
|
}
|
|
38633
|
-
async function startMapTool(ws, port) {
|
|
39086
|
+
async function startMapTool(ws, port, authToken, platformServer, platform3) {
|
|
38634
39087
|
try {
|
|
38635
|
-
const { startMapServer } = await import("@arcote.tech/arc-map");
|
|
39088
|
+
const { startMapServer, MAP_SCHEMA_VERSION: MAP_SCHEMA_VERSION2 } = await import("@arcote.tech/arc-map");
|
|
38636
39089
|
const { getContext } = await import("@arcote.tech/platform");
|
|
38637
|
-
const globs = ws.packages.map((p3) =>
|
|
38638
|
-
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);
|
|
38639
39093
|
const map = await startMapServer({
|
|
38640
39094
|
getContext: () => getContext(),
|
|
38641
39095
|
scan: { globs, workspaceRoot: ws.rootDir, packageOf },
|
|
38642
39096
|
useCasePackages: ws.packages.map((p3) => ({ name: p3.name, path: p3.path })),
|
|
38643
|
-
port
|
|
39097
|
+
port,
|
|
39098
|
+
authToken,
|
|
39099
|
+
meta,
|
|
39100
|
+
platform: platform3
|
|
38644
39101
|
});
|
|
39102
|
+
platformServer.setMapUrl(map.url);
|
|
38645
39103
|
ok(`Context map \u2192 ${map.url}`);
|
|
39104
|
+
ok(`Pairing token: ${authToken}`);
|
|
38646
39105
|
return map.notifyReload;
|
|
38647
39106
|
} catch (e2) {
|
|
38648
39107
|
err(`Context map failed to start: ${e2.message}`);
|
|
38649
39108
|
return;
|
|
38650
39109
|
}
|
|
38651
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
|
+
}
|
|
38652
39178
|
function attachDevWatcher(ws, platform3, onReload) {
|
|
38653
39179
|
log2("Watching for changes...");
|
|
38654
39180
|
let rebuildTimer = null;
|
|
@@ -38675,8 +39201,8 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38675
39201
|
}, 300);
|
|
38676
39202
|
};
|
|
38677
39203
|
for (const pkg of ws.packages) {
|
|
38678
|
-
const srcDir =
|
|
38679
|
-
if (!
|
|
39204
|
+
const srcDir = join23(pkg.path, "src");
|
|
39205
|
+
if (!existsSync20(srcDir))
|
|
38680
39206
|
continue;
|
|
38681
39207
|
watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
38682
39208
|
if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
|
|
@@ -38686,8 +39212,8 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38686
39212
|
triggerRebuild();
|
|
38687
39213
|
});
|
|
38688
39214
|
}
|
|
38689
|
-
const localesDir =
|
|
38690
|
-
if (
|
|
39215
|
+
const localesDir = join23(ws.rootDir, "locales");
|
|
39216
|
+
if (existsSync20(localesDir)) {
|
|
38691
39217
|
watch(localesDir, { recursive: false }, (_event, filename) => {
|
|
38692
39218
|
if (!filename?.endsWith(".po"))
|
|
38693
39219
|
return;
|
|
@@ -38699,7 +39225,12 @@ function attachDevWatcher(ws, platform3, onReload) {
|
|
|
38699
39225
|
// src/commands/platform-dev.ts
|
|
38700
39226
|
async function platformDev(opts = {}) {
|
|
38701
39227
|
const ws = resolveWorkspace();
|
|
38702
|
-
await startPlatform({
|
|
39228
|
+
await startPlatform({
|
|
39229
|
+
ws,
|
|
39230
|
+
devMode: true,
|
|
39231
|
+
map: opts.map,
|
|
39232
|
+
headless: opts.headless
|
|
39233
|
+
});
|
|
38703
39234
|
}
|
|
38704
39235
|
|
|
38705
39236
|
// src/commands/platform-start.ts
|
|
@@ -38714,7 +39245,11 @@ program2.name("arc").description("CLI tool for Arc framework").version("0.0.3");
|
|
|
38714
39245
|
program2.command("dev").description("Run development mode for Arc framework").action(dev);
|
|
38715
39246
|
program2.command("build").description("Build all clients and declarations").action(build);
|
|
38716
39247
|
var platform3 = program2.command("platform").description("Platform commands \u2014 run full stack (server + UI)");
|
|
38717
|
-
platform3.command("dev").description("Start platform in dev mode (Bun server + Vite HMR)").option("--no-cache", "Force full rebuild on startup").option("--map", "Also start the Arc Context Map dev tool on a separate port").
|
|
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
|
+
}));
|
|
38718
39253
|
platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
|
|
38719
39254
|
platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
|
|
38720
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));
|