@arcote.tech/arc-cli 0.7.20 → 0.7.22
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/dist/index.js +225 -123
- package/package.json +9 -9
- package/src/builder/access-extractor.ts +11 -15
- package/src/builder/module-builder.ts +210 -35
- package/src/commands/platform-deploy.ts +42 -35
- package/src/deploy/deploy-env.ts +23 -46
- package/src/deploy/observability-configs.ts +14 -0
- package/src/deploy/remote-state.ts +39 -20
- package/src/deploy/ssh.ts +49 -0
- package/src/platform/server.ts +20 -3
- package/src/platform/shared.ts +34 -73
- package/src/platform/startup.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -853,7 +853,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
853
853
|
this._exitCallback = (err) => {
|
|
854
854
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
855
855
|
throw err;
|
|
856
|
-
}
|
|
856
|
+
}
|
|
857
857
|
};
|
|
858
858
|
}
|
|
859
859
|
return this;
|
|
@@ -34723,6 +34723,17 @@ function serverExternalsPlugin() {
|
|
|
34723
34723
|
}
|
|
34724
34724
|
};
|
|
34725
34725
|
}
|
|
34726
|
+
function workspaceSourcePlugin(srcByName) {
|
|
34727
|
+
return {
|
|
34728
|
+
name: "workspace-source",
|
|
34729
|
+
setup(build2) {
|
|
34730
|
+
build2.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
34731
|
+
const src = srcByName.get(args.path);
|
|
34732
|
+
return src ? { path: src, sideEffects: true } : null;
|
|
34733
|
+
});
|
|
34734
|
+
}
|
|
34735
|
+
};
|
|
34736
|
+
}
|
|
34726
34737
|
function jsxDevShimPlugin() {
|
|
34727
34738
|
return {
|
|
34728
34739
|
name: "jsx-dev-runtime-shim",
|
|
@@ -34743,9 +34754,10 @@ export { Fragment };
|
|
|
34743
34754
|
};
|
|
34744
34755
|
}
|
|
34745
34756
|
var CONTEXT_CLIENTS = [
|
|
34746
|
-
{ name: "server", target: "bun", defines: { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } },
|
|
34747
34757
|
{ name: "browser", target: "browser", defines: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" } }
|
|
34748
34758
|
];
|
|
34759
|
+
var SERVER_DEFINES = { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" };
|
|
34760
|
+
var SERVER_ENTRY_FILE = "_server.js";
|
|
34749
34761
|
function discoverPackages(rootDir) {
|
|
34750
34762
|
const rootPkg = JSON.parse(readFileSync7(join8(rootDir, "package.json"), "utf-8"));
|
|
34751
34763
|
const workspaceGlobs = rootPkg.workspaces ?? [];
|
|
@@ -34838,9 +34850,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
34838
34850
|
console.log(` building: ${pkg.name} (${client.name})`);
|
|
34839
34851
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
34840
34852
|
const allDeps = pkg.packageJson.dependencies ?? {};
|
|
34841
|
-
const
|
|
34842
|
-
const workspaceDeps = isBrowser2 ? Object.keys(allDeps) : Object.entries(allDeps).filter(([, spec]) => !spec.startsWith("workspace:")).map(([name]) => name);
|
|
34843
|
-
const externals = [...peerDeps, ...workspaceDeps];
|
|
34853
|
+
const externals = [...peerDeps, ...Object.keys(allDeps)];
|
|
34844
34854
|
const result = await Bun.build({
|
|
34845
34855
|
entrypoints: [pkg.entrypoint],
|
|
34846
34856
|
outdir: join8(outDir, "main"),
|
|
@@ -34848,7 +34858,7 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
34848
34858
|
format: "esm",
|
|
34849
34859
|
naming: "index.[ext]",
|
|
34850
34860
|
external: externals,
|
|
34851
|
-
plugins:
|
|
34861
|
+
plugins: [jsxDevShimPlugin()],
|
|
34852
34862
|
define: client.defines
|
|
34853
34863
|
});
|
|
34854
34864
|
if (!result.success) {
|
|
@@ -34911,6 +34921,80 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
|
|
|
34911
34921
|
}
|
|
34912
34922
|
return { declarationErrors };
|
|
34913
34923
|
}
|
|
34924
|
+
async function buildServerApp(rootDir, serverDir, packages, cache, noCache) {
|
|
34925
|
+
const contexts = packages.filter((p) => isContextPackage(p.packageJson));
|
|
34926
|
+
mkdirSync6(serverDir, { recursive: true });
|
|
34927
|
+
const srcByName = new Map(packages.map((p) => [p.name, p.entrypoint]));
|
|
34928
|
+
const externalSet = new Set(FRAMEWORK_PEERS);
|
|
34929
|
+
for (const p of packages) {
|
|
34930
|
+
for (const name of Object.keys(p.packageJson.peerDependencies ?? {})) {
|
|
34931
|
+
externalSet.add(name);
|
|
34932
|
+
}
|
|
34933
|
+
for (const [name, spec] of Object.entries(p.packageJson.dependencies ?? {})) {
|
|
34934
|
+
if (!spec.startsWith("workspace:"))
|
|
34935
|
+
externalSet.add(name);
|
|
34936
|
+
}
|
|
34937
|
+
}
|
|
34938
|
+
const external = [...externalSet];
|
|
34939
|
+
const unitId = "server-app";
|
|
34940
|
+
const inputHash = sha256OfJson({
|
|
34941
|
+
members: packages.map((p) => ({ name: p.name, src: pkgSourceHash(p) })).sort((a, b) => a.name.localeCompare(b.name)),
|
|
34942
|
+
contexts: contexts.map((p) => p.name).sort(),
|
|
34943
|
+
external: [...external].sort(),
|
|
34944
|
+
defines: SERVER_DEFINES
|
|
34945
|
+
});
|
|
34946
|
+
const entryFileAbs = join8(serverDir, SERVER_ENTRY_FILE);
|
|
34947
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [entryFileAbs])) {
|
|
34948
|
+
console.log(` \u2713 cached: ${unitId}`);
|
|
34949
|
+
return { entryFile: SERVER_ENTRY_FILE, cached: true };
|
|
34950
|
+
}
|
|
34951
|
+
console.log(` building: ${unitId} (${contexts.length} server modules)`);
|
|
34952
|
+
for (const f of readdirSync4(serverDir)) {
|
|
34953
|
+
if (f.endsWith(".js"))
|
|
34954
|
+
rmSync(join8(serverDir, f), { force: true });
|
|
34955
|
+
}
|
|
34956
|
+
const tmpDir = join8(serverDir, "_entries");
|
|
34957
|
+
mkdirSync6(tmpDir, { recursive: true });
|
|
34958
|
+
const entrySrc = join8(tmpDir, SERVER_ENTRY_FILE.replace(/\.js$/, ".ts"));
|
|
34959
|
+
writeFileSync6(entrySrc, contexts.map((p) => `import "${p.name}";`).join(`
|
|
34960
|
+
`) + `
|
|
34961
|
+
`);
|
|
34962
|
+
let result;
|
|
34963
|
+
try {
|
|
34964
|
+
result = await Bun.build({
|
|
34965
|
+
entrypoints: [entrySrc],
|
|
34966
|
+
outdir: serverDir,
|
|
34967
|
+
target: "bun",
|
|
34968
|
+
format: "esm",
|
|
34969
|
+
splitting: true,
|
|
34970
|
+
naming: "[name].[ext]",
|
|
34971
|
+
external,
|
|
34972
|
+
plugins: [
|
|
34973
|
+
jsxDevShimPlugin(),
|
|
34974
|
+
serverExternalsPlugin(),
|
|
34975
|
+
workspaceSourcePlugin(srcByName)
|
|
34976
|
+
],
|
|
34977
|
+
define: SERVER_DEFINES
|
|
34978
|
+
});
|
|
34979
|
+
} finally {
|
|
34980
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
34981
|
+
}
|
|
34982
|
+
if (!result.success) {
|
|
34983
|
+
for (const log2 of result.logs)
|
|
34984
|
+
console.error(log2);
|
|
34985
|
+
throw new Error("Server app build failed");
|
|
34986
|
+
}
|
|
34987
|
+
const entryOut = result.outputs.find((o) => o.kind === "entry-point");
|
|
34988
|
+
if (!entryOut) {
|
|
34989
|
+
throw new Error("Server app build: entry not found in outputs");
|
|
34990
|
+
}
|
|
34991
|
+
if (basename2(entryOut.path) !== SERVER_ENTRY_FILE) {
|
|
34992
|
+
throw new Error(`Server app build: unexpected entry name ${basename2(entryOut.path)} (wanted ${SERVER_ENTRY_FILE})`);
|
|
34993
|
+
}
|
|
34994
|
+
const outputHash = sha256OfDir(serverDir);
|
|
34995
|
+
updateCache(cache, unitId, inputHash, { outputHash });
|
|
34996
|
+
return { entryFile: SERVER_ENTRY_FILE, cached: false };
|
|
34997
|
+
}
|
|
34914
34998
|
async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector) {
|
|
34915
34999
|
mkdirSync6(outDir, { recursive: true });
|
|
34916
35000
|
const publicMembers = plan.groups.get("public") ?? [];
|
|
@@ -35216,11 +35300,8 @@ import {
|
|
|
35216
35300
|
writeFileSync as writeFileSync7
|
|
35217
35301
|
} from "fs";
|
|
35218
35302
|
import { join as join9 } from "path";
|
|
35219
|
-
async function extractAccessMap(rootDir,
|
|
35220
|
-
const serverBundles =
|
|
35221
|
-
name: p.name,
|
|
35222
|
-
path: join9(p.path, "dist", "server", "main", "index.js")
|
|
35223
|
-
})).filter((b) => existsSync8(b.path));
|
|
35303
|
+
async function extractAccessMap(rootDir, serverBundlePath) {
|
|
35304
|
+
const serverBundles = existsSync8(serverBundlePath) ? [{ name: "server", path: serverBundlePath }] : [];
|
|
35224
35305
|
const workerDir = join9(rootDir, ".arc", ".tmp");
|
|
35225
35306
|
mkdirSync7(workerDir, { recursive: true });
|
|
35226
35307
|
const workerPath = join9(workerDir, `access-extractor-${Date.now()}.mjs`);
|
|
@@ -35549,8 +35630,9 @@ async function buildAll(ws, opts = {}) {
|
|
|
35549
35630
|
log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
35550
35631
|
assertOneModulePerPackage(ws.packages);
|
|
35551
35632
|
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
35552
|
-
|
|
35553
|
-
const
|
|
35633
|
+
const serverDir = join12(ws.arcDir, "server");
|
|
35634
|
+
const { entryFile: serverEntry } = await buildServerApp(ws.rootDir, serverDir, ws.packages, cache, noCache);
|
|
35635
|
+
const accessMap = await extractAccessMap(ws.rootDir, join12(serverDir, serverEntry));
|
|
35554
35636
|
mkdirSync9(ws.arcDir, { recursive: true });
|
|
35555
35637
|
writeFileSync9(join12(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
|
|
35556
35638
|
`);
|
|
@@ -35581,22 +35663,6 @@ function assembleManifest(ws, browser, cache) {
|
|
|
35581
35663
|
buildTime: new Date().toISOString()
|
|
35582
35664
|
};
|
|
35583
35665
|
}
|
|
35584
|
-
function copyContextServerBundles(ws) {
|
|
35585
|
-
const outDir = join12(ws.arcDir, "server");
|
|
35586
|
-
mkdirSync9(outDir, { recursive: true });
|
|
35587
|
-
for (const pkg of ws.packages) {
|
|
35588
|
-
if (!isContextPackage(pkg.packageJson))
|
|
35589
|
-
continue;
|
|
35590
|
-
const src = join12(pkg.path, "dist", "server", "main", "index.js");
|
|
35591
|
-
if (!existsSync10(src)) {
|
|
35592
|
-
err(`Server bundle missing for ${pkg.name}: ${src}`);
|
|
35593
|
-
continue;
|
|
35594
|
-
}
|
|
35595
|
-
const safeName = pkg.path.split("/").pop();
|
|
35596
|
-
const dst = join12(outDir, `${safeName}.js`);
|
|
35597
|
-
copyFileSync(src, dst);
|
|
35598
|
-
}
|
|
35599
|
-
}
|
|
35600
35666
|
function resolveAssetSource(from, pkgDir, rootDir) {
|
|
35601
35667
|
if (from.startsWith("./") || from.startsWith("../")) {
|
|
35602
35668
|
const resolved = join12(pkgDir, from);
|
|
@@ -35692,34 +35758,15 @@ async function loadServerContext(ws) {
|
|
|
35692
35758
|
const platformPkg = JSON.parse(readFileSync11(join12(platformDir, "package.json"), "utf-8"));
|
|
35693
35759
|
const platformEntry = join12(platformDir, platformPkg.main ?? "src/index.ts");
|
|
35694
35760
|
await import(platformEntry);
|
|
35695
|
-
const
|
|
35696
|
-
|
|
35697
|
-
if (bundles.length > 0) {
|
|
35698
|
-
for (const file of bundles) {
|
|
35699
|
-
const bundlePath = join12(serverDir, file);
|
|
35700
|
-
try {
|
|
35701
|
-
await import(bundlePath);
|
|
35702
|
-
} catch (e) {
|
|
35703
|
-
err(`Failed to load server bundle ${file}: ${e}`);
|
|
35704
|
-
}
|
|
35705
|
-
}
|
|
35706
|
-
} else if (ws.packages.length > 0) {
|
|
35707
|
-
const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
|
|
35708
|
-
for (const ctx of ctxPackages) {
|
|
35709
|
-
const serverDist = join12(ctx.path, "dist", "server", "main", "index.js");
|
|
35710
|
-
if (!existsSync10(serverDist)) {
|
|
35711
|
-
err(`Context server dist not found: ${serverDist}`);
|
|
35712
|
-
continue;
|
|
35713
|
-
}
|
|
35714
|
-
try {
|
|
35715
|
-
await import(serverDist);
|
|
35716
|
-
} catch (e) {
|
|
35717
|
-
err(`Failed to load server context from ${ctx.name}: ${e}`);
|
|
35718
|
-
}
|
|
35719
|
-
}
|
|
35720
|
-
} else {
|
|
35761
|
+
const serverEntry = join12(ws.arcDir, "server", SERVER_ENTRY_FILE);
|
|
35762
|
+
if (!existsSync10(serverEntry)) {
|
|
35721
35763
|
return { context: null, moduleAccess: new Map };
|
|
35722
35764
|
}
|
|
35765
|
+
try {
|
|
35766
|
+
await import(serverEntry);
|
|
35767
|
+
} catch (e) {
|
|
35768
|
+
err(`Failed to load server bundle ${SERVER_ENTRY_FILE}: ${e}`);
|
|
35769
|
+
}
|
|
35723
35770
|
const { getContext, getAllModuleAccess } = await import(platformEntry);
|
|
35724
35771
|
return {
|
|
35725
35772
|
context: getContext() ?? null,
|
|
@@ -36553,8 +36600,13 @@ ${envNames.map((name) => ` - "https://${cfg.envs[name].domain}"`).joi
|
|
|
36553
36600
|
|
|
36554
36601
|
# Per-container CPU / memory / network / block-IO + restarts straight from
|
|
36555
36602
|
# the Docker daemon (socket bind-mounted read-only, see compose).
|
|
36603
|
+
# api_version pinned: the receiver defaults to Docker API 1.25, which modern
|
|
36604
|
+
# daemons (Engine 25+ require >= 1.40) reject \u2014 without this the receiver
|
|
36605
|
+
# fails to start and takes the whole collector down. Quoted so YAML doesn't
|
|
36606
|
+
# parse 1.40 \u2192 1.4. Must be <= the daemon's max; 1.40 is the safe floor.
|
|
36556
36607
|
docker_stats:
|
|
36557
36608
|
endpoint: unix:///var/run/docker.sock
|
|
36609
|
+
api_version: "1.40"
|
|
36558
36610
|
collection_interval: 30s
|
|
36559
36611
|
metrics:
|
|
36560
36612
|
container.restarts:
|
|
@@ -36824,6 +36876,15 @@ function generateAlloyConfig() {
|
|
|
36824
36876
|
discovery.docker "containers" {
|
|
36825
36877
|
host = "unix:///var/run/docker.sock"
|
|
36826
36878
|
refresh_interval = "15s"
|
|
36879
|
+
|
|
36880
|
+
// Only containers managed by a compose project (our stack). Ad-hoc / rogue
|
|
36881
|
+
// containers (manual debug runs, other stacks) are excluded \u2014 one bad
|
|
36882
|
+
// stream (e.g. log entries older than Loki's reject window) otherwise 400s
|
|
36883
|
+
// the whole loki.write batch and drops good app logs with it.
|
|
36884
|
+
filter {
|
|
36885
|
+
name = "label"
|
|
36886
|
+
values = ["com.docker.compose.project"]
|
|
36887
|
+
}
|
|
36827
36888
|
}
|
|
36828
36889
|
|
|
36829
36890
|
discovery.relabel "containers" {
|
|
@@ -38030,9 +38091,20 @@ async function streamToString(stream2) {
|
|
|
38030
38091
|
return "";
|
|
38031
38092
|
return new Response(stream2).text();
|
|
38032
38093
|
}
|
|
38094
|
+
function sshMuxArgs() {
|
|
38095
|
+
return [
|
|
38096
|
+
"-o",
|
|
38097
|
+
"ControlMaster=auto",
|
|
38098
|
+
"-o",
|
|
38099
|
+
`ControlPath=${join17(homedir3(), ".ssh", "cm-arc-%C")}`,
|
|
38100
|
+
"-o",
|
|
38101
|
+
"ControlPersist=120"
|
|
38102
|
+
];
|
|
38103
|
+
}
|
|
38033
38104
|
function baseSshArgs(target) {
|
|
38034
38105
|
const key = pickSshKey(target);
|
|
38035
38106
|
const args = [
|
|
38107
|
+
...sshMuxArgs(),
|
|
38036
38108
|
"-o",
|
|
38037
38109
|
"BatchMode=yes",
|
|
38038
38110
|
"-o",
|
|
@@ -38096,6 +38168,7 @@ async function canSsh(target) {
|
|
|
38096
38168
|
async function scpUpload(target, localPath, remotePath) {
|
|
38097
38169
|
const key = pickSshKey(target);
|
|
38098
38170
|
const args = [
|
|
38171
|
+
...sshMuxArgs(),
|
|
38099
38172
|
"-o",
|
|
38100
38173
|
"BatchMode=yes",
|
|
38101
38174
|
"-o",
|
|
@@ -38120,6 +38193,22 @@ async function scpUpload(target, localPath, remotePath) {
|
|
|
38120
38193
|
throw new Error(`scp failed (${exitCode}): ${stderr}`);
|
|
38121
38194
|
}
|
|
38122
38195
|
}
|
|
38196
|
+
async function closeSshMaster(target) {
|
|
38197
|
+
try {
|
|
38198
|
+
const proc2 = spawn3({
|
|
38199
|
+
cmd: [
|
|
38200
|
+
"ssh",
|
|
38201
|
+
...baseSshArgs(target),
|
|
38202
|
+
"-O",
|
|
38203
|
+
"exit",
|
|
38204
|
+
`${target.user}@${target.host}`
|
|
38205
|
+
],
|
|
38206
|
+
stdout: "ignore",
|
|
38207
|
+
stderr: "ignore"
|
|
38208
|
+
});
|
|
38209
|
+
await proc2.exited;
|
|
38210
|
+
} catch {}
|
|
38211
|
+
}
|
|
38123
38212
|
|
|
38124
38213
|
// src/deploy/remote-state.ts
|
|
38125
38214
|
var STATE_MARKER_PATH = "/opt/arc/.arc-state.json";
|
|
@@ -38127,38 +38216,55 @@ async function detectRemoteState(cfg) {
|
|
|
38127
38216
|
if (cfg.target.host === "PENDING_TERRAFORM" || !cfg.target.host) {
|
|
38128
38217
|
return { kind: "unreachable", reason: "target.host not yet set" };
|
|
38129
38218
|
}
|
|
38130
|
-
|
|
38219
|
+
const composeDir = cfg.target.remoteDir;
|
|
38220
|
+
const probe = [
|
|
38221
|
+
`command -v docker >/dev/null 2>&1 && echo DOCKER=1 || echo DOCKER=0`,
|
|
38222
|
+
`echo '---PS---'`,
|
|
38223
|
+
`[ -f ${composeDir}/docker-compose.yml ] && (cd ${composeDir} && docker compose ps --format '{{.Service}}' 2>/dev/null) || true`,
|
|
38224
|
+
`echo '---MARKER---'`,
|
|
38225
|
+
`cat ${STATE_MARKER_PATH} 2>/dev/null || true`
|
|
38226
|
+
].join(`
|
|
38227
|
+
`);
|
|
38228
|
+
const res = await sshExec(cfg.target, probe, { quiet: true });
|
|
38229
|
+
if (res.exitCode !== 0) {
|
|
38131
38230
|
if (await canSsh({ ...cfg.target, user: "root" })) {
|
|
38132
38231
|
return { kind: "no-docker" };
|
|
38133
38232
|
}
|
|
38134
38233
|
return { kind: "unreachable", reason: "ssh connection failed" };
|
|
38135
38234
|
}
|
|
38136
|
-
const
|
|
38137
|
-
|
|
38138
|
-
});
|
|
38139
|
-
if (dockerCheck.exitCode !== 0) {
|
|
38235
|
+
const out = res.stdout;
|
|
38236
|
+
if (!out.includes("DOCKER=1")) {
|
|
38140
38237
|
return { kind: "no-docker" };
|
|
38141
38238
|
}
|
|
38142
|
-
const
|
|
38143
|
-
|
|
38144
|
-
if (psCheck.exitCode !== 0 || psCheck.stdout.trim() === "") {
|
|
38239
|
+
const psSection = sectionBetween(out, "---PS---", "---MARKER---").trim();
|
|
38240
|
+
if (psSection === "") {
|
|
38145
38241
|
return { kind: "no-stack" };
|
|
38146
38242
|
}
|
|
38147
|
-
const running =
|
|
38243
|
+
const running = psSection.split(`
|
|
38148
38244
|
`).map((l) => l.trim()).filter((l) => l.startsWith("arc-")).map((l) => l.replace(/^arc-/, ""));
|
|
38149
|
-
const
|
|
38150
|
-
quiet: true
|
|
38151
|
-
});
|
|
38245
|
+
const markerSection = afterMarker(out, "---MARKER---").trim();
|
|
38152
38246
|
let marker = null;
|
|
38153
|
-
if (
|
|
38247
|
+
if (markerSection) {
|
|
38154
38248
|
try {
|
|
38155
|
-
marker = JSON.parse(
|
|
38249
|
+
marker = JSON.parse(markerSection);
|
|
38156
38250
|
} catch {
|
|
38157
38251
|
marker = null;
|
|
38158
38252
|
}
|
|
38159
38253
|
}
|
|
38160
38254
|
return { kind: "ready", runningEnvs: running, marker };
|
|
38161
38255
|
}
|
|
38256
|
+
function sectionBetween(s, start, end) {
|
|
38257
|
+
const i = s.indexOf(start);
|
|
38258
|
+
if (i < 0)
|
|
38259
|
+
return "";
|
|
38260
|
+
const from = i + start.length;
|
|
38261
|
+
const j = s.indexOf(end, from);
|
|
38262
|
+
return s.slice(from, j < 0 ? undefined : j);
|
|
38263
|
+
}
|
|
38264
|
+
function afterMarker(s, marker) {
|
|
38265
|
+
const i = s.indexOf(marker);
|
|
38266
|
+
return i < 0 ? "" : s.slice(i + marker.length);
|
|
38267
|
+
}
|
|
38162
38268
|
async function writeStateMarker(target, marker) {
|
|
38163
38269
|
const json = JSON.stringify(marker, null, 2);
|
|
38164
38270
|
await assertExec(target, `sudo tee ${STATE_MARKER_PATH} > /dev/null <<'JSON'
|
|
@@ -38415,31 +38521,20 @@ async function updateEnvDeployment(opts) {
|
|
|
38415
38521
|
const envVarName = `ARC_IMAGE_${upperEnv}`;
|
|
38416
38522
|
const envPath = `${cfg.target.remoteDir}/.env`;
|
|
38417
38523
|
const escapedRef = fullRef.replace(/"/g, "\\\"");
|
|
38418
|
-
const updateScript = [
|
|
38419
|
-
`touch ${envPath} && `,
|
|
38420
|
-
`awk -v line="${envVarName}=${escapedRef}" -v key="${envVarName}=" '`,
|
|
38421
|
-
` BEGIN { replaced=0 } `,
|
|
38422
|
-
` $0 ~ "^"key { print line; replaced=1; next } `,
|
|
38423
|
-
` { print } `,
|
|
38424
|
-
` END { if (!replaced) print line } `,
|
|
38425
|
-
`' ${envPath} > ${envPath}.tmp && mv ${envPath}.tmp ${envPath}`
|
|
38426
|
-
].join("");
|
|
38427
|
-
await assertExec(target, updateScript);
|
|
38428
|
-
await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose pull arc-${env2}`);
|
|
38429
|
-
await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose up -d arc-${env2}`);
|
|
38430
38524
|
const retain = opts.retainImages ?? 3;
|
|
38431
38525
|
const imageBaseName = imageBaseFromRef(fullRef);
|
|
38432
|
-
|
|
38433
|
-
|
|
38434
|
-
|
|
38435
|
-
|
|
38436
|
-
|
|
38437
|
-
|
|
38438
|
-
|
|
38439
|
-
|
|
38440
|
-
|
|
38441
|
-
|
|
38442
|
-
|
|
38526
|
+
const pruneCmd = imageBaseName ? `docker images "${imageBaseName}" --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" | grep -v ":latest " | sort -k2,3 -r | tail -n +${retain + 1} | awk '{print $1}' | xargs -r docker rmi 2>/dev/null || true` : `true`;
|
|
38527
|
+
const script = [
|
|
38528
|
+
`set -e`,
|
|
38529
|
+
`cd ${cfg.target.remoteDir}`,
|
|
38530
|
+
`touch ${envPath}`,
|
|
38531
|
+
`awk -v line="${envVarName}=${escapedRef}" -v key="${envVarName}=" 'BEGIN{replaced=0} $0 ~ "^"key {print line; replaced=1; next} {print} END{if(!replaced) print line}' ${envPath} > ${envPath}.tmp && mv ${envPath}.tmp ${envPath}`,
|
|
38532
|
+
`docker compose pull arc-${env2}`,
|
|
38533
|
+
`docker compose up -d arc-${env2}`,
|
|
38534
|
+
pruneCmd
|
|
38535
|
+
].join(`
|
|
38536
|
+
`);
|
|
38537
|
+
await assertExec(target, script);
|
|
38443
38538
|
const ok2 = await healthCheck(target, env2);
|
|
38444
38539
|
return { env: env2, fullRef, redeployed: ok2 };
|
|
38445
38540
|
}
|
|
@@ -39480,38 +39575,42 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
39480
39575
|
}
|
|
39481
39576
|
}
|
|
39482
39577
|
log2("Inspecting remote server...");
|
|
39483
|
-
|
|
39484
|
-
|
|
39485
|
-
|
|
39486
|
-
|
|
39487
|
-
|
|
39488
|
-
|
|
39489
|
-
rootDir: ws.rootDir,
|
|
39490
|
-
state,
|
|
39491
|
-
cliVersion,
|
|
39492
|
-
configHash,
|
|
39493
|
-
forceAnsible: options.forceBootstrap
|
|
39494
|
-
});
|
|
39495
|
-
if (!options.imageTag) {
|
|
39496
|
-
log2(`Logging in to ${cfg.registry.domain}...`);
|
|
39497
|
-
await dockerLogin(cfg.registry);
|
|
39498
|
-
log2(`Pushing ${fullRef}...`);
|
|
39499
|
-
await dockerPush(fullRef);
|
|
39500
|
-
ok("Image pushed");
|
|
39501
|
-
}
|
|
39502
|
-
for (const env2 of targetEnvs) {
|
|
39503
|
-
log2(`Updating env "${env2}"...`);
|
|
39504
|
-
const outcome = await updateEnvDeployment({
|
|
39505
|
-
target: cfg.target,
|
|
39578
|
+
try {
|
|
39579
|
+
const state = await detectRemoteState(cfg);
|
|
39580
|
+
log2(`Remote state: ${state.kind}`);
|
|
39581
|
+
const cliVersion = readCliVersion2();
|
|
39582
|
+
const configHash = await hashDeployConfig(ws.rootDir);
|
|
39583
|
+
await bootstrap({
|
|
39506
39584
|
cfg,
|
|
39507
|
-
|
|
39508
|
-
|
|
39585
|
+
rootDir: ws.rootDir,
|
|
39586
|
+
state,
|
|
39587
|
+
cliVersion,
|
|
39588
|
+
configHash,
|
|
39589
|
+
forceAnsible: options.forceBootstrap
|
|
39509
39590
|
});
|
|
39510
|
-
if (
|
|
39511
|
-
|
|
39512
|
-
|
|
39513
|
-
|
|
39591
|
+
if (!options.imageTag) {
|
|
39592
|
+
log2(`Logging in to ${cfg.registry.domain}...`);
|
|
39593
|
+
await dockerLogin(cfg.registry);
|
|
39594
|
+
log2(`Pushing ${fullRef}...`);
|
|
39595
|
+
await dockerPush(fullRef);
|
|
39596
|
+
ok("Image pushed");
|
|
39597
|
+
}
|
|
39598
|
+
for (const env2 of targetEnvs) {
|
|
39599
|
+
log2(`Updating env "${env2}"...`);
|
|
39600
|
+
const outcome = await updateEnvDeployment({
|
|
39601
|
+
target: cfg.target,
|
|
39602
|
+
cfg,
|
|
39603
|
+
env: env2,
|
|
39604
|
+
fullRef
|
|
39605
|
+
});
|
|
39606
|
+
if (outcome.redeployed) {
|
|
39607
|
+
ok(`${env2}: live at ${fullRef}`);
|
|
39608
|
+
} else {
|
|
39609
|
+
err(`${env2}: deployed but health check did not pass within retries \u2014 check \`docker logs arc-${env2}\``);
|
|
39610
|
+
}
|
|
39514
39611
|
}
|
|
39612
|
+
} finally {
|
|
39613
|
+
await closeSshMaster(cfg.target);
|
|
39515
39614
|
}
|
|
39516
39615
|
}
|
|
39517
39616
|
function readCliVersion2() {
|
|
@@ -40790,6 +40889,7 @@ async function createArcServer(config) {
|
|
|
40790
40889
|
const cronScheduler = new CronScheduler(contextHandler);
|
|
40791
40890
|
cronScheduler.start();
|
|
40792
40891
|
const connectionManager = new ConnectionManager;
|
|
40892
|
+
const coep = config.coep ?? "unsafe-none";
|
|
40793
40893
|
function buildCorsHeaders(req) {
|
|
40794
40894
|
const origin = req?.headers.get("Origin") || "*";
|
|
40795
40895
|
return {
|
|
@@ -40798,7 +40898,7 @@ async function createArcServer(config) {
|
|
|
40798
40898
|
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Scope, X-Arc-Tokens",
|
|
40799
40899
|
"Access-Control-Allow-Credentials": "true",
|
|
40800
40900
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
40801
|
-
"Cross-Origin-Embedder-Policy":
|
|
40901
|
+
"Cross-Origin-Embedder-Policy": coep,
|
|
40802
40902
|
"Cross-Origin-Resource-Policy": "cross-origin"
|
|
40803
40903
|
};
|
|
40804
40904
|
}
|
|
@@ -41259,6 +41359,7 @@ function spaFallbackHandler(getShellHtml) {
|
|
|
41259
41359
|
}
|
|
41260
41360
|
async function startPlatformServer(opts) {
|
|
41261
41361
|
const { ws, port, devMode, context: context2 } = opts;
|
|
41362
|
+
const coep = process.env.ARC_COEP ?? opts.coep ?? "unsafe-none";
|
|
41262
41363
|
ensureModuleSigSecret(ws, !!devMode);
|
|
41263
41364
|
let telemetry;
|
|
41264
41365
|
let telemetryShutdown;
|
|
@@ -41307,7 +41408,7 @@ async function startPlatformServer(opts) {
|
|
|
41307
41408
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
41308
41409
|
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Arc-Tokens",
|
|
41309
41410
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
41310
|
-
"Cross-Origin-Embedder-Policy": "
|
|
41411
|
+
"Cross-Origin-Embedder-Policy": coep ?? "unsafe-none",
|
|
41311
41412
|
"Cross-Origin-Resource-Policy": "cross-origin"
|
|
41312
41413
|
};
|
|
41313
41414
|
const server = Bun.serve({
|
|
@@ -41359,6 +41460,7 @@ async function startPlatformServer(opts) {
|
|
|
41359
41460
|
context: context2,
|
|
41360
41461
|
dbAdapterFactory,
|
|
41361
41462
|
port,
|
|
41463
|
+
coep,
|
|
41362
41464
|
httpHandlers: [
|
|
41363
41465
|
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap, telemetry),
|
|
41364
41466
|
devReloadHandler(sseClients),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.22",
|
|
4
4
|
"description": "CLI tool for Arc framework",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"build": "bun build --target=bun ./src/index.ts --outdir=dist --external @arcote.tech/arc --external @arcote.tech/arc-ds --external @arcote.tech/arc-react --external @arcote.tech/platform --external '@opentelemetry/*' && chmod +x dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@arcote.tech/arc": "^0.7.
|
|
16
|
-
"@arcote.tech/arc-ds": "^0.7.
|
|
17
|
-
"@arcote.tech/arc-react": "^0.7.
|
|
18
|
-
"@arcote.tech/arc-host": "^0.7.
|
|
19
|
-
"@arcote.tech/arc-adapter-db-sqlite": "^0.7.
|
|
20
|
-
"@arcote.tech/arc-adapter-db-postgres": "^0.7.
|
|
21
|
-
"@arcote.tech/arc-otel": "^0.7.
|
|
15
|
+
"@arcote.tech/arc": "^0.7.22",
|
|
16
|
+
"@arcote.tech/arc-ds": "^0.7.22",
|
|
17
|
+
"@arcote.tech/arc-react": "^0.7.22",
|
|
18
|
+
"@arcote.tech/arc-host": "^0.7.22",
|
|
19
|
+
"@arcote.tech/arc-adapter-db-sqlite": "^0.7.22",
|
|
20
|
+
"@arcote.tech/arc-adapter-db-postgres": "^0.7.22",
|
|
21
|
+
"@arcote.tech/arc-otel": "^0.7.22",
|
|
22
22
|
"@opentelemetry/api": "^1.9.0",
|
|
23
23
|
"@opentelemetry/api-logs": "^0.57.0",
|
|
24
24
|
"@opentelemetry/core": "^1.30.0",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@opentelemetry/sdk-trace-base": "^1.30.0",
|
|
32
32
|
"@opentelemetry/sdk-trace-node": "^1.30.0",
|
|
33
33
|
"@opentelemetry/semantic-conventions": "^1.27.0",
|
|
34
|
-
"@arcote.tech/platform": "^0.7.
|
|
34
|
+
"@arcote.tech/platform": "^0.7.22",
|
|
35
35
|
"@clack/prompts": "^0.9.0",
|
|
36
36
|
"commander": "^11.1.0",
|
|
37
37
|
"chokidar": "^3.5.3",
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
writeFileSync,
|
|
8
8
|
} from "fs";
|
|
9
9
|
import { join } from "path";
|
|
10
|
-
import { isContextPackage, type WorkspacePackage } from "./module-builder";
|
|
11
10
|
|
|
12
11
|
// ---------------------------------------------------------------------------
|
|
13
12
|
// access-extractor — discovers per-module access rules (`protectedBy(...)`)
|
|
@@ -27,8 +26,8 @@ import { isContextPackage, type WorkspacePackage } from "./module-builder";
|
|
|
27
26
|
// non-context (pure-browser) packages are skipped. This is sensible: access
|
|
28
27
|
// checks logically belong with server state, not display components.
|
|
29
28
|
//
|
|
30
|
-
// Order requirement:
|
|
31
|
-
//
|
|
29
|
+
// Order requirement: buildServerApp MUST run before extractAccessMap — the
|
|
30
|
+
// combined server bundle at `<arcDir>/server/_server.js` must exist.
|
|
32
31
|
// ---------------------------------------------------------------------------
|
|
33
32
|
|
|
34
33
|
export interface SerializedAccessRule {
|
|
@@ -44,19 +43,16 @@ export type SerializedAccessMap = Record<string, SerializedModuleAccess>;
|
|
|
44
43
|
|
|
45
44
|
export async function extractAccessMap(
|
|
46
45
|
rootDir: string,
|
|
47
|
-
|
|
46
|
+
serverBundlePath: string,
|
|
48
47
|
): Promise<SerializedAccessMap> {
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
path: join(p.path, "dist", "server", "main", "index.js"),
|
|
58
|
-
}))
|
|
59
|
-
.filter((b) => existsSync(b.path));
|
|
48
|
+
// The combined server bundle (`<arcDir>/server/_server.js`) side-effect-
|
|
49
|
+
// registers every module via the platform singleton when imported. The
|
|
50
|
+
// worker imports just this entry; Bun resolves its `chunk-<hash>.js` siblings
|
|
51
|
+
// and external peers (`@arcote.tech/platform` etc.) by walking node_modules
|
|
52
|
+
// from the bundle's directory.
|
|
53
|
+
const serverBundles = existsSync(serverBundlePath)
|
|
54
|
+
? [{ name: "server", path: serverBundlePath }]
|
|
55
|
+
: [];
|
|
60
56
|
|
|
61
57
|
// Worker must live INSIDE the workspace tree so Bun's module resolver can
|
|
62
58
|
// walk up to <rootDir>/node_modules and find @arcote.tech/platform via the
|