@arcote.tech/arc-cli 0.6.2 → 0.7.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/dist/index.js +1213 -1219
- package/package.json +7 -7
- package/src/builder/access-extractor.ts +79 -47
- package/src/builder/build-cache.ts +3 -1
- package/src/builder/chunk-planner.ts +107 -0
- package/src/builder/dependency-collector.ts +83 -41
- package/src/builder/framework-peers.ts +81 -0
- package/src/builder/module-builder.ts +186 -110
- package/src/commands/platform-deploy.ts +103 -55
- package/src/commands/platform-dev.ts +11 -100
- package/src/commands/platform-start.ts +4 -90
- package/src/deploy/bootstrap.ts +157 -6
- package/src/deploy/caddyfile.ts +19 -23
- package/src/deploy/compose.ts +43 -27
- package/src/deploy/config.ts +29 -0
- package/src/deploy/deploy-env.ts +129 -0
- package/src/deploy/htpasswd.ts +28 -0
- package/src/deploy/image-template.ts +74 -0
- package/src/deploy/image.ts +237 -0
- package/src/deploy/registry.ts +79 -0
- package/src/deploy/ssh.ts +5 -124
- package/src/deploy/survey.ts +64 -0
- package/src/index.ts +15 -13
- package/src/platform/server.ts +69 -44
- package/src/platform/shared.ts +124 -65
- package/src/platform/startup.ts +160 -0
- package/runtime/Dockerfile +0 -29
- package/runtime/build-and-push.sh +0 -23
- package/runtime/entrypoint.sh +0 -58
- package/src/commands/build-shell.ts +0 -152
- package/src/deploy/remote-sync.ts +0 -321
- package/src/platform/deploy-api.ts +0 -400
package/dist/index.js
CHANGED
|
@@ -9744,9 +9744,9 @@ var require_verify_stream = __commonJS((exports, module) => {
|
|
|
9744
9744
|
}
|
|
9745
9745
|
function jwsVerify(jwsSig, algorithm, secretOrKey) {
|
|
9746
9746
|
if (!algorithm) {
|
|
9747
|
-
var
|
|
9748
|
-
|
|
9749
|
-
throw
|
|
9747
|
+
var err2 = new Error("Missing algorithm parameter for jws.verify");
|
|
9748
|
+
err2.code = "MISSING_ALGORITHM";
|
|
9749
|
+
throw err2;
|
|
9750
9750
|
}
|
|
9751
9751
|
jwsSig = toString(jwsSig);
|
|
9752
9752
|
var signature = signatureFromJWS(jwsSig);
|
|
@@ -11934,9 +11934,9 @@ var require_verify = __commonJS((exports, module) => {
|
|
|
11934
11934
|
if (callback) {
|
|
11935
11935
|
done = callback;
|
|
11936
11936
|
} else {
|
|
11937
|
-
done = function(
|
|
11938
|
-
if (
|
|
11939
|
-
throw
|
|
11937
|
+
done = function(err2, data) {
|
|
11938
|
+
if (err2)
|
|
11939
|
+
throw err2;
|
|
11940
11940
|
return data;
|
|
11941
11941
|
};
|
|
11942
11942
|
}
|
|
@@ -11963,8 +11963,8 @@ var require_verify = __commonJS((exports, module) => {
|
|
|
11963
11963
|
let decodedToken;
|
|
11964
11964
|
try {
|
|
11965
11965
|
decodedToken = decode(jwtString, { complete: true });
|
|
11966
|
-
} catch (
|
|
11967
|
-
return done(
|
|
11966
|
+
} catch (err2) {
|
|
11967
|
+
return done(err2);
|
|
11968
11968
|
}
|
|
11969
11969
|
if (!decodedToken) {
|
|
11970
11970
|
return done(new JsonWebTokenError("invalid token"));
|
|
@@ -11981,9 +11981,9 @@ var require_verify = __commonJS((exports, module) => {
|
|
|
11981
11981
|
return secretCallback(null, secretOrPublicKey);
|
|
11982
11982
|
};
|
|
11983
11983
|
}
|
|
11984
|
-
return getSecret(header, function(
|
|
11985
|
-
if (
|
|
11986
|
-
return done(new JsonWebTokenError("error in secret or public key callback: " +
|
|
11984
|
+
return getSecret(header, function(err2, secretOrPublicKey2) {
|
|
11985
|
+
if (err2) {
|
|
11986
|
+
return done(new JsonWebTokenError("error in secret or public key callback: " + err2.message));
|
|
11987
11987
|
}
|
|
11988
11988
|
const hasSignature = parts[2].trim() !== "";
|
|
11989
11989
|
if (!hasSignature && secretOrPublicKey2) {
|
|
@@ -12619,11 +12619,11 @@ var require_sign = __commonJS((exports, module) => {
|
|
|
12619
12619
|
typ: isObjectPayload ? "JWT" : undefined,
|
|
12620
12620
|
kid: options.keyid
|
|
12621
12621
|
}, options.header);
|
|
12622
|
-
function failure(
|
|
12622
|
+
function failure(err2) {
|
|
12623
12623
|
if (callback) {
|
|
12624
|
-
return callback(
|
|
12624
|
+
return callback(err2);
|
|
12625
12625
|
}
|
|
12626
|
-
throw
|
|
12626
|
+
throw err2;
|
|
12627
12627
|
}
|
|
12628
12628
|
if (!secretOrPrivateKey && options.algorithm !== "none") {
|
|
12629
12629
|
return failure(new Error("secretOrPrivateKey must have a value"));
|
|
@@ -12695,8 +12695,8 @@ var require_sign = __commonJS((exports, module) => {
|
|
|
12695
12695
|
if (typeof options.notBefore !== "undefined") {
|
|
12696
12696
|
try {
|
|
12697
12697
|
payload.nbf = timespan(options.notBefore, timestamp2);
|
|
12698
|
-
} catch (
|
|
12699
|
-
return failure(
|
|
12698
|
+
} catch (err2) {
|
|
12699
|
+
return failure(err2);
|
|
12700
12700
|
}
|
|
12701
12701
|
if (typeof payload.nbf === "undefined") {
|
|
12702
12702
|
return failure(new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));
|
|
@@ -12705,8 +12705,8 @@ var require_sign = __commonJS((exports, module) => {
|
|
|
12705
12705
|
if (typeof options.expiresIn !== "undefined" && typeof payload === "object") {
|
|
12706
12706
|
try {
|
|
12707
12707
|
payload.exp = timespan(options.expiresIn, timestamp2);
|
|
12708
|
-
} catch (
|
|
12709
|
-
return failure(
|
|
12708
|
+
} catch (err2) {
|
|
12709
|
+
return failure(err2);
|
|
12710
12710
|
}
|
|
12711
12711
|
if (typeof payload.exp === "undefined") {
|
|
12712
12712
|
return failure(new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));
|
|
@@ -13764,8 +13764,8 @@ class EventWire {
|
|
|
13764
13764
|
try {
|
|
13765
13765
|
const message = JSON.parse(event.data);
|
|
13766
13766
|
this.handleMessage(message);
|
|
13767
|
-
} catch (
|
|
13768
|
-
console.error("EventWire: Failed to parse message",
|
|
13767
|
+
} catch (err2) {
|
|
13768
|
+
console.error("EventWire: Failed to parse message", err2);
|
|
13769
13769
|
}
|
|
13770
13770
|
};
|
|
13771
13771
|
this.ws.onclose = (event) => {
|
|
@@ -13774,17 +13774,17 @@ class EventWire {
|
|
|
13774
13774
|
console.log("EventWire disconnected");
|
|
13775
13775
|
this.scheduleReconnect();
|
|
13776
13776
|
};
|
|
13777
|
-
this.ws.onerror = (
|
|
13778
|
-
console.error("EventWire error:",
|
|
13777
|
+
this.ws.onerror = (err2) => {
|
|
13778
|
+
console.error("EventWire error:", err2);
|
|
13779
13779
|
if (this.state === "connecting") {
|
|
13780
13780
|
this.state = "disconnected";
|
|
13781
13781
|
this.ws = null;
|
|
13782
13782
|
}
|
|
13783
13783
|
};
|
|
13784
|
-
} catch (
|
|
13784
|
+
} catch (err2) {
|
|
13785
13785
|
this.state = "disconnected";
|
|
13786
13786
|
this.ws = null;
|
|
13787
|
-
console.error("EventWire: Failed to connect",
|
|
13787
|
+
console.error("EventWire: Failed to connect", err2);
|
|
13788
13788
|
this.scheduleReconnect();
|
|
13789
13789
|
}
|
|
13790
13790
|
}
|
|
@@ -18135,7 +18135,7 @@ var {
|
|
|
18135
18135
|
// ../../node_modules/.bun/find-up@7.0.0/node_modules/find-up/index.js
|
|
18136
18136
|
import path2 from "path";
|
|
18137
18137
|
|
|
18138
|
-
// ../../node_modules/.bun/
|
|
18138
|
+
// ../../node_modules/.bun/locate-path@7.2.0/node_modules/locate-path/index.js
|
|
18139
18139
|
import process2 from "process";
|
|
18140
18140
|
import path from "path";
|
|
18141
18141
|
import fs, { promises as fsPromises } from "fs";
|
|
@@ -21686,7 +21686,7 @@ var emitWarning = (msg, type, code, fn) => {
|
|
|
21686
21686
|
var AC = globalThis.AbortController;
|
|
21687
21687
|
var AS = globalThis.AbortSignal;
|
|
21688
21688
|
if (typeof AC === "undefined") {
|
|
21689
|
-
AS = class
|
|
21689
|
+
AS = class AbortSignal {
|
|
21690
21690
|
onabort;
|
|
21691
21691
|
_onabort = [];
|
|
21692
21692
|
reason;
|
|
@@ -26129,110 +26129,9 @@ ${colors2.yellow}Type declaration errors:${colors2.reset}`);
|
|
|
26129
26129
|
}
|
|
26130
26130
|
}
|
|
26131
26131
|
|
|
26132
|
-
// src/commands/build-shell.ts
|
|
26133
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, rmSync } from "fs";
|
|
26134
|
-
import { join as join3 } from "path";
|
|
26135
|
-
var REACT_ENTRIES = [
|
|
26136
|
-
["react", `export * from "react";
|
|
26137
|
-
import * as React from "react";
|
|
26138
|
-
export default React;`],
|
|
26139
|
-
["react-dom", `export * from "react-dom";
|
|
26140
|
-
import * as ReactDOM from "react-dom";
|
|
26141
|
-
export default ReactDOM;`],
|
|
26142
|
-
["jsx-runtime", `export * from "react/jsx-runtime";`],
|
|
26143
|
-
["jsx-dev-runtime", `export * from "react/jsx-dev-runtime";`],
|
|
26144
|
-
["react-dom-client", `export { createRoot, hydrateRoot } from "react-dom/client";`]
|
|
26145
|
-
];
|
|
26146
|
-
var SHELL_BASE_EXTERNAL = [
|
|
26147
|
-
"react",
|
|
26148
|
-
"react-dom",
|
|
26149
|
-
"react/jsx-runtime",
|
|
26150
|
-
"react/jsx-dev-runtime",
|
|
26151
|
-
"react-dom/client"
|
|
26152
|
-
];
|
|
26153
|
-
async function buildShell(opts) {
|
|
26154
|
-
const outDir = opts.out;
|
|
26155
|
-
const fromDir = opts.from;
|
|
26156
|
-
if (!existsSync3(fromDir)) {
|
|
26157
|
-
console.error(`[_build-shell] --from not found: ${fromDir}`);
|
|
26158
|
-
process.exit(1);
|
|
26159
|
-
}
|
|
26160
|
-
mkdirSync3(outDir, { recursive: true });
|
|
26161
|
-
const tmpDir = join3(outDir, "_tmp");
|
|
26162
|
-
mkdirSync3(tmpDir, { recursive: true });
|
|
26163
|
-
try {
|
|
26164
|
-
const arcPkgs = discoverArcPackages(fromDir);
|
|
26165
|
-
if (arcPkgs.length === 0) {
|
|
26166
|
-
console.warn("[_build-shell] no @arcote.tech/* packages discovered");
|
|
26167
|
-
}
|
|
26168
|
-
console.log(`[_build-shell] building shell for react + ${arcPkgs.length} @arcote.tech/* package(s)`);
|
|
26169
|
-
await buildReactShell(outDir, tmpDir, fromDir);
|
|
26170
|
-
for (const pkg of arcPkgs) {
|
|
26171
|
-
await buildArcEntry(pkg, arcPkgs, outDir, tmpDir, fromDir);
|
|
26172
|
-
}
|
|
26173
|
-
console.log(`[_build-shell] done \u2192 ${outDir}`);
|
|
26174
|
-
} finally {
|
|
26175
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
26176
|
-
}
|
|
26177
|
-
}
|
|
26178
|
-
function discoverArcPackages(fromDir) {
|
|
26179
|
-
const arcDir = join3(fromDir, "@arcote.tech");
|
|
26180
|
-
if (!existsSync3(arcDir))
|
|
26181
|
-
return [];
|
|
26182
|
-
return readdirSync2(arcDir).filter((name) => existsSync3(join3(arcDir, name, "package.json"))).map((name) => `@arcote.tech/${name}`);
|
|
26183
|
-
}
|
|
26184
|
-
async function buildReactShell(outDir, tmpDir, fromDir) {
|
|
26185
|
-
const eps = [];
|
|
26186
|
-
for (const [name, code] of REACT_ENTRIES) {
|
|
26187
|
-
const f = join3(tmpDir, `${name}.ts`);
|
|
26188
|
-
await Bun.write(f, code);
|
|
26189
|
-
eps.push(f);
|
|
26190
|
-
}
|
|
26191
|
-
const r = await Bun.build({
|
|
26192
|
-
entrypoints: eps,
|
|
26193
|
-
outdir: outDir,
|
|
26194
|
-
splitting: true,
|
|
26195
|
-
format: "esm",
|
|
26196
|
-
target: "browser",
|
|
26197
|
-
naming: "[name].[ext]",
|
|
26198
|
-
root: fromDir
|
|
26199
|
-
});
|
|
26200
|
-
if (!r.success) {
|
|
26201
|
-
for (const l of r.logs)
|
|
26202
|
-
console.error(l);
|
|
26203
|
-
throw new Error("React shell build failed");
|
|
26204
|
-
}
|
|
26205
|
-
}
|
|
26206
|
-
async function buildArcEntry(pkg, allArcPkgs, outDir, tmpDir, fromDir) {
|
|
26207
|
-
const shortName = pkg.replace("@arcote.tech/", "");
|
|
26208
|
-
const otherExternals = allArcPkgs.filter((p) => p !== pkg);
|
|
26209
|
-
const f = join3(tmpDir, `${shortName}.ts`);
|
|
26210
|
-
await Bun.write(f, `export * from "${pkg}";
|
|
26211
|
-
`);
|
|
26212
|
-
const r = await Bun.build({
|
|
26213
|
-
entrypoints: [f],
|
|
26214
|
-
outdir: outDir,
|
|
26215
|
-
format: "esm",
|
|
26216
|
-
target: "browser",
|
|
26217
|
-
naming: "[name].[ext]",
|
|
26218
|
-
root: fromDir,
|
|
26219
|
-
external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
|
|
26220
|
-
define: {
|
|
26221
|
-
ONLY_SERVER: "false",
|
|
26222
|
-
ONLY_BROWSER: "true",
|
|
26223
|
-
ONLY_CLIENT: "true"
|
|
26224
|
-
}
|
|
26225
|
-
});
|
|
26226
|
-
if (!r.success) {
|
|
26227
|
-
for (const l of r.logs)
|
|
26228
|
-
console.error(l);
|
|
26229
|
-
throw new Error(`Shell build failed for ${pkg}`);
|
|
26230
|
-
}
|
|
26231
|
-
}
|
|
26232
|
-
|
|
26233
26132
|
// src/commands/dev.ts
|
|
26234
26133
|
var import_chokidar = __toESM(require_chokidar(), 1);
|
|
26235
|
-
import { dirname as dirname3, join as
|
|
26134
|
+
import { dirname as dirname3, join as join3, relative } from "path";
|
|
26236
26135
|
function getContextForFile(filePath, contexts, configDir) {
|
|
26237
26136
|
const relativePath = relative(configDir, filePath);
|
|
26238
26137
|
for (const context of contexts) {
|
|
@@ -26349,7 +26248,7 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
|
|
|
26349
26248
|
} else {
|
|
26350
26249
|
log("Initial build complete \u2713", "green");
|
|
26351
26250
|
}
|
|
26352
|
-
const watcher = import_chokidar.default.watch([
|
|
26251
|
+
const watcher = import_chokidar.default.watch([join3(configDir, "**/*.ts"), join3(configDir, "**/*.tsx")], {
|
|
26353
26252
|
ignored: [
|
|
26354
26253
|
"**/node_modules/**",
|
|
26355
26254
|
`**/${config.outDir}/**`,
|
|
@@ -26446,24 +26345,24 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
|
|
|
26446
26345
|
}
|
|
26447
26346
|
|
|
26448
26347
|
// src/platform/shared.ts
|
|
26449
|
-
import { copyFileSync, existsSync as existsSync10, mkdirSync as
|
|
26450
|
-
import { dirname as
|
|
26348
|
+
import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync10, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
26349
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
26451
26350
|
|
|
26452
26351
|
// src/builder/module-builder.ts
|
|
26453
26352
|
import { execSync } from "child_process";
|
|
26454
26353
|
import {
|
|
26455
|
-
existsSync as
|
|
26456
|
-
mkdirSync as
|
|
26457
|
-
readFileSync as
|
|
26458
|
-
readdirSync as
|
|
26459
|
-
rmSync
|
|
26460
|
-
writeFileSync as
|
|
26354
|
+
existsSync as existsSync7,
|
|
26355
|
+
mkdirSync as mkdirSync6,
|
|
26356
|
+
readFileSync as readFileSync7,
|
|
26357
|
+
readdirSync as readdirSync4,
|
|
26358
|
+
rmSync,
|
|
26359
|
+
writeFileSync as writeFileSync6
|
|
26461
26360
|
} from "fs";
|
|
26462
|
-
import { basename as basename2, dirname as dirname5, join as
|
|
26361
|
+
import { basename as basename2, dirname as dirname5, join as join8, relative as relative3 } from "path";
|
|
26463
26362
|
|
|
26464
26363
|
// src/i18n/index.ts
|
|
26465
|
-
import { existsSync as
|
|
26466
|
-
import { dirname as dirname4, join as
|
|
26364
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
26365
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
26467
26366
|
|
|
26468
26367
|
// src/i18n/catalog.ts
|
|
26469
26368
|
function hashMsgid(msgid) {
|
|
@@ -26634,10 +26533,10 @@ function quoteString(s) {
|
|
|
26634
26533
|
}
|
|
26635
26534
|
|
|
26636
26535
|
// src/i18n/compile.ts
|
|
26637
|
-
import { mkdirSync as
|
|
26638
|
-
import { join as
|
|
26536
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
26537
|
+
import { join as join4 } from "path";
|
|
26639
26538
|
function compileCatalog(poPath) {
|
|
26640
|
-
const content =
|
|
26539
|
+
const content = readFileSync3(poPath, "utf-8");
|
|
26641
26540
|
const entries = parsePo(content);
|
|
26642
26541
|
const result = {};
|
|
26643
26542
|
for (const entry of entries) {
|
|
@@ -26652,13 +26551,13 @@ function compileCatalog(poPath) {
|
|
|
26652
26551
|
return sorted;
|
|
26653
26552
|
}
|
|
26654
26553
|
function compileAllCatalogs(localesDir, outDir) {
|
|
26655
|
-
|
|
26656
|
-
for (const file of
|
|
26554
|
+
mkdirSync3(outDir, { recursive: true });
|
|
26555
|
+
for (const file of readdirSync2(localesDir)) {
|
|
26657
26556
|
if (!file.endsWith(".po"))
|
|
26658
26557
|
continue;
|
|
26659
26558
|
const locale = file.replace(".po", "");
|
|
26660
|
-
const compiled = compileCatalog(
|
|
26661
|
-
|
|
26559
|
+
const compiled = compileCatalog(join4(localesDir, file));
|
|
26560
|
+
writeFileSync3(join4(outDir, `${locale}.json`), JSON.stringify(compiled));
|
|
26662
26561
|
}
|
|
26663
26562
|
}
|
|
26664
26563
|
|
|
@@ -26702,10 +26601,10 @@ function i18nExtractPlugin(collector, rootDir) {
|
|
|
26702
26601
|
|
|
26703
26602
|
// src/i18n/index.ts
|
|
26704
26603
|
function readTranslationsConfig(rootDir) {
|
|
26705
|
-
const pkgPath =
|
|
26706
|
-
if (!
|
|
26604
|
+
const pkgPath = join5(rootDir, "package.json");
|
|
26605
|
+
if (!existsSync4(pkgPath))
|
|
26707
26606
|
return null;
|
|
26708
|
-
const pkg = JSON.parse(
|
|
26607
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
26709
26608
|
const config = pkg.arc?.translations;
|
|
26710
26609
|
if (!config?.locales?.length)
|
|
26711
26610
|
return null;
|
|
@@ -26718,34 +26617,34 @@ async function finalizeTranslations(rootDir, outDir, collector) {
|
|
|
26718
26617
|
const config = readTranslationsConfig(rootDir);
|
|
26719
26618
|
if (!config || collector.size === 0)
|
|
26720
26619
|
return;
|
|
26721
|
-
const localesJsonDir =
|
|
26722
|
-
|
|
26620
|
+
const localesJsonDir = join5(outDir, "locales");
|
|
26621
|
+
mkdirSync4(localesJsonDir, { recursive: true });
|
|
26723
26622
|
console.log(` Extracted ${collector.size} translatable string(s) for ${config.locales.length} locale(s)`);
|
|
26724
26623
|
for (const locale of config.locales) {
|
|
26725
|
-
const poPath =
|
|
26726
|
-
|
|
26727
|
-
const existing =
|
|
26624
|
+
const poPath = join5(rootDir, "locales", `${locale}.po`);
|
|
26625
|
+
mkdirSync4(dirname4(poPath), { recursive: true });
|
|
26626
|
+
const existing = existsSync4(poPath) ? parsePo(readFileSync4(poPath, "utf-8")) : [];
|
|
26728
26627
|
const merged = mergeCatalog(existing, collector);
|
|
26729
|
-
|
|
26628
|
+
writeFileSync4(poPath, writePo(merged));
|
|
26730
26629
|
const compiled = compileCatalog(poPath);
|
|
26731
|
-
|
|
26630
|
+
writeFileSync4(join5(localesJsonDir, `${locale}.json`), JSON.stringify(compiled));
|
|
26732
26631
|
}
|
|
26733
26632
|
}
|
|
26734
26633
|
|
|
26735
26634
|
// src/builder/build-cache.ts
|
|
26736
|
-
import { existsSync as
|
|
26737
|
-
import { join as
|
|
26738
|
-
var CACHE_VERSION =
|
|
26635
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
26636
|
+
import { join as join6 } from "path";
|
|
26637
|
+
var CACHE_VERSION = 2;
|
|
26739
26638
|
var CACHE_FILE = ".build-cache.json";
|
|
26740
26639
|
function emptyCache() {
|
|
26741
26640
|
return { version: CACHE_VERSION, units: {} };
|
|
26742
26641
|
}
|
|
26743
26642
|
function loadBuildCache(arcDir) {
|
|
26744
|
-
const path4 =
|
|
26745
|
-
if (!
|
|
26643
|
+
const path4 = join6(arcDir, CACHE_FILE);
|
|
26644
|
+
if (!existsSync5(path4))
|
|
26746
26645
|
return emptyCache();
|
|
26747
26646
|
try {
|
|
26748
|
-
const raw = JSON.parse(
|
|
26647
|
+
const raw = JSON.parse(readFileSync5(path4, "utf-8"));
|
|
26749
26648
|
if (raw?.version !== CACHE_VERSION || typeof raw.units !== "object") {
|
|
26750
26649
|
return emptyCache();
|
|
26751
26650
|
}
|
|
@@ -26755,15 +26654,15 @@ function loadBuildCache(arcDir) {
|
|
|
26755
26654
|
}
|
|
26756
26655
|
}
|
|
26757
26656
|
function saveBuildCache(arcDir, cache) {
|
|
26758
|
-
|
|
26759
|
-
|
|
26657
|
+
mkdirSync5(arcDir, { recursive: true });
|
|
26658
|
+
writeFileSync5(join6(arcDir, CACHE_FILE), JSON.stringify(cache, null, 2));
|
|
26760
26659
|
}
|
|
26761
26660
|
function isCacheHit(cache, unitId, inputHash, requiredOutputs = []) {
|
|
26762
26661
|
const entry = cache.units[unitId];
|
|
26763
26662
|
if (!entry || entry.inputHash !== inputHash)
|
|
26764
26663
|
return false;
|
|
26765
26664
|
for (const out of requiredOutputs) {
|
|
26766
|
-
if (!
|
|
26665
|
+
if (!existsSync5(out))
|
|
26767
26666
|
return false;
|
|
26768
26667
|
}
|
|
26769
26668
|
return true;
|
|
@@ -26776,9 +26675,33 @@ function updateCache(cache, unitId, inputHash, output = {}) {
|
|
|
26776
26675
|
};
|
|
26777
26676
|
}
|
|
26778
26677
|
|
|
26678
|
+
// src/builder/framework-peers.ts
|
|
26679
|
+
var CORE_PEERS = [
|
|
26680
|
+
"@arcote.tech/arc",
|
|
26681
|
+
"@arcote.tech/arc-ds",
|
|
26682
|
+
"@arcote.tech/arc-react",
|
|
26683
|
+
"@arcote.tech/platform"
|
|
26684
|
+
];
|
|
26685
|
+
var BROWSER_FRAGMENT_PEERS = [
|
|
26686
|
+
"@arcote.tech/arc-auth",
|
|
26687
|
+
"@arcote.tech/arc-workspace",
|
|
26688
|
+
"@arcote.tech/arc-utils",
|
|
26689
|
+
"@arcote.tech/arc-chat"
|
|
26690
|
+
];
|
|
26691
|
+
var ARC_PEERS = [...CORE_PEERS, ...BROWSER_FRAGMENT_PEERS];
|
|
26692
|
+
var REACT_PEERS = ["react", "react-dom"];
|
|
26693
|
+
var FRAMEWORK_PEERS = [...ARC_PEERS, ...REACT_PEERS];
|
|
26694
|
+
var SHELL_EXTERNALS = [
|
|
26695
|
+
...FRAMEWORK_PEERS,
|
|
26696
|
+
"react/jsx-runtime",
|
|
26697
|
+
"react/jsx-dev-runtime"
|
|
26698
|
+
];
|
|
26699
|
+
var FRAMEWORK_PEER_SET = new Set(FRAMEWORK_PEERS);
|
|
26700
|
+
var SHELL_EXTERNAL_SET = new Set(SHELL_EXTERNALS);
|
|
26701
|
+
|
|
26779
26702
|
// src/builder/hash.ts
|
|
26780
|
-
import { existsSync as
|
|
26781
|
-
import { join as
|
|
26703
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync } from "fs";
|
|
26704
|
+
import { join as join7, relative as relative2, sep as sep2 } from "path";
|
|
26782
26705
|
function sha256Hex(bytes) {
|
|
26783
26706
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
26784
26707
|
hasher.update(bytes);
|
|
@@ -26788,21 +26711,21 @@ function sha256OfFiles(paths) {
|
|
|
26788
26711
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
26789
26712
|
const sorted = [...paths].sort();
|
|
26790
26713
|
for (const p of sorted) {
|
|
26791
|
-
if (!
|
|
26714
|
+
if (!existsSync6(p))
|
|
26792
26715
|
continue;
|
|
26793
|
-
hasher.update(
|
|
26716
|
+
hasher.update(readFileSync6(p));
|
|
26794
26717
|
hasher.update("\x00");
|
|
26795
26718
|
}
|
|
26796
26719
|
return hasher.digest("hex");
|
|
26797
26720
|
}
|
|
26798
26721
|
function sha256OfDir(dir, filter2) {
|
|
26799
|
-
if (!
|
|
26722
|
+
if (!existsSync6(dir))
|
|
26800
26723
|
return sha256Hex("");
|
|
26801
26724
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
26802
26725
|
const entries = [];
|
|
26803
26726
|
function walk(absDir) {
|
|
26804
|
-
for (const entry of
|
|
26805
|
-
const abs =
|
|
26727
|
+
for (const entry of readdirSync3(absDir, { withFileTypes: true })) {
|
|
26728
|
+
const abs = join7(absDir, entry.name);
|
|
26806
26729
|
const rel = relative2(dir, abs).split(sep2).join("/");
|
|
26807
26730
|
if (filter2 && !filter2(rel))
|
|
26808
26731
|
continue;
|
|
@@ -26817,7 +26740,7 @@ function sha256OfDir(dir, filter2) {
|
|
|
26817
26740
|
for (const { rel, abs } of entries) {
|
|
26818
26741
|
hasher.update(rel);
|
|
26819
26742
|
hasher.update("\x00");
|
|
26820
|
-
hasher.update(
|
|
26743
|
+
hasher.update(readFileSync6(abs));
|
|
26821
26744
|
hasher.update("\x00");
|
|
26822
26745
|
}
|
|
26823
26746
|
return hasher.digest("hex");
|
|
@@ -26836,17 +26759,17 @@ function stableStringify(value) {
|
|
|
26836
26759
|
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
26837
26760
|
}
|
|
26838
26761
|
function readInstalledVersion(rootDir, pkgName) {
|
|
26839
|
-
const pkgJson =
|
|
26840
|
-
if (!
|
|
26762
|
+
const pkgJson = join7(rootDir, "node_modules", pkgName, "package.json");
|
|
26763
|
+
if (!existsSync6(pkgJson))
|
|
26841
26764
|
return null;
|
|
26842
26765
|
try {
|
|
26843
|
-
return JSON.parse(
|
|
26766
|
+
return JSON.parse(readFileSync6(pkgJson, "utf-8")).version ?? null;
|
|
26844
26767
|
} catch {
|
|
26845
26768
|
return null;
|
|
26846
26769
|
}
|
|
26847
26770
|
}
|
|
26848
26771
|
function mtimeOf(path4) {
|
|
26849
|
-
if (!
|
|
26772
|
+
if (!existsSync6(path4))
|
|
26850
26773
|
return 0;
|
|
26851
26774
|
try {
|
|
26852
26775
|
return statSync(path4).mtimeMs;
|
|
@@ -26876,58 +26799,64 @@ async function pAll(tasks, concurrency = DEFAULT_CONCURRENCY) {
|
|
|
26876
26799
|
}
|
|
26877
26800
|
|
|
26878
26801
|
// src/builder/module-builder.ts
|
|
26802
|
+
function jsxDevShimPlugin() {
|
|
26803
|
+
return {
|
|
26804
|
+
name: "jsx-dev-runtime-shim",
|
|
26805
|
+
setup(build2) {
|
|
26806
|
+
build2.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
|
|
26807
|
+
path: "react-jsx-dev-runtime-shim",
|
|
26808
|
+
namespace: "jsx-dev-shim"
|
|
26809
|
+
}));
|
|
26810
|
+
build2.onLoad({ filter: /.*/, namespace: "jsx-dev-shim" }, () => ({
|
|
26811
|
+
contents: `import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
26812
|
+
export const jsxDEV = jsx;
|
|
26813
|
+
export const jsxsDEV = jsxs;
|
|
26814
|
+
export { Fragment };
|
|
26815
|
+
`,
|
|
26816
|
+
loader: "ts"
|
|
26817
|
+
}));
|
|
26818
|
+
}
|
|
26819
|
+
};
|
|
26820
|
+
}
|
|
26879
26821
|
var CONTEXT_CLIENTS = [
|
|
26880
26822
|
{ name: "server", target: "bun", defines: { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } },
|
|
26881
26823
|
{ name: "browser", target: "browser", defines: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" } }
|
|
26882
26824
|
];
|
|
26883
|
-
var SHELL_EXTERNALS = [
|
|
26884
|
-
"react",
|
|
26885
|
-
"react-dom",
|
|
26886
|
-
"react/jsx-runtime",
|
|
26887
|
-
"react/jsx-dev-runtime",
|
|
26888
|
-
"@arcote.tech/arc",
|
|
26889
|
-
"@arcote.tech/arc-ds",
|
|
26890
|
-
"@arcote.tech/arc-react",
|
|
26891
|
-
"@arcote.tech/arc-auth",
|
|
26892
|
-
"@arcote.tech/arc-utils",
|
|
26893
|
-
"@arcote.tech/arc-workspace",
|
|
26894
|
-
"@arcote.tech/platform"
|
|
26895
|
-
];
|
|
26896
26825
|
function discoverPackages(rootDir) {
|
|
26897
|
-
const rootPkg = JSON.parse(
|
|
26826
|
+
const rootPkg = JSON.parse(readFileSync7(join8(rootDir, "package.json"), "utf-8"));
|
|
26898
26827
|
const workspaceGlobs = rootPkg.workspaces ?? [];
|
|
26899
26828
|
const results = [];
|
|
26900
26829
|
for (const glob2 of workspaceGlobs) {
|
|
26901
26830
|
const base2 = glob2.replace("/*", "");
|
|
26902
|
-
const baseDir =
|
|
26903
|
-
if (!
|
|
26831
|
+
const baseDir = join8(rootDir, base2);
|
|
26832
|
+
if (!existsSync7(baseDir))
|
|
26904
26833
|
continue;
|
|
26905
26834
|
let entries;
|
|
26906
26835
|
try {
|
|
26907
|
-
entries =
|
|
26836
|
+
entries = readdirSync4(baseDir);
|
|
26908
26837
|
} catch {
|
|
26909
26838
|
continue;
|
|
26910
26839
|
}
|
|
26911
26840
|
for (const entry of entries) {
|
|
26912
|
-
const pkgPath =
|
|
26913
|
-
if (!
|
|
26841
|
+
const pkgPath = join8(baseDir, entry, "package.json");
|
|
26842
|
+
if (!existsSync7(pkgPath))
|
|
26914
26843
|
continue;
|
|
26915
|
-
const pkg = JSON.parse(
|
|
26844
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
26916
26845
|
if (pkg.name?.startsWith("@arcote.tech/"))
|
|
26917
26846
|
continue;
|
|
26918
|
-
const pkgDir =
|
|
26847
|
+
const pkgDir = join8(baseDir, entry);
|
|
26919
26848
|
const candidates = [
|
|
26920
|
-
|
|
26921
|
-
|
|
26922
|
-
|
|
26923
|
-
|
|
26849
|
+
join8(pkgDir, "src", "index.ts"),
|
|
26850
|
+
join8(pkgDir, "src", "index.tsx"),
|
|
26851
|
+
join8(pkgDir, "index.ts"),
|
|
26852
|
+
join8(pkgDir, "index.tsx")
|
|
26924
26853
|
];
|
|
26925
|
-
const entrypoint = candidates.find((c) =>
|
|
26854
|
+
const entrypoint = candidates.find((c) => existsSync7(c)) ?? null;
|
|
26926
26855
|
if (!entrypoint)
|
|
26927
26856
|
continue;
|
|
26928
26857
|
results.push({
|
|
26929
26858
|
name: pkg.name,
|
|
26930
|
-
path:
|
|
26859
|
+
path: join8(baseDir, entry),
|
|
26931
26860
|
entrypoint,
|
|
26932
26861
|
packageJson: pkg
|
|
26933
26862
|
});
|
|
@@ -26956,7 +26885,7 @@ var sourceFilter = (rel) => {
|
|
|
26956
26885
|
return true;
|
|
26957
26886
|
};
|
|
26958
26887
|
function pkgSourceHash(pkg) {
|
|
26959
|
-
return sha256OfDir(
|
|
26888
|
+
return sha256OfDir(join8(pkg.path, "src"), sourceFilter);
|
|
26960
26889
|
}
|
|
26961
26890
|
function depVersionsHash(rootDir, pkg) {
|
|
26962
26891
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
@@ -26969,7 +26898,7 @@ function depVersionsHash(rootDir, pkg) {
|
|
|
26969
26898
|
}
|
|
26970
26899
|
async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
26971
26900
|
const unitId = `context-pkg:${pkg.name}:${client.name}`;
|
|
26972
|
-
const outDir =
|
|
26901
|
+
const outDir = join8(pkg.path, "dist", client.name);
|
|
26973
26902
|
const inputHash = sha256OfJson({
|
|
26974
26903
|
src: pkgSourceHash(pkg),
|
|
26975
26904
|
pkg: pkg.packageJson,
|
|
@@ -26978,21 +26907,23 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
26978
26907
|
target: client.target,
|
|
26979
26908
|
defines: client.defines
|
|
26980
26909
|
});
|
|
26981
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
26910
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(outDir, "main", "index.js")])) {
|
|
26982
26911
|
console.log(` \u2713 cached: ${pkg.name} (${client.name})`);
|
|
26983
26912
|
return { pkgName: pkg.name, client: client.name, declarationErrors: [], cached: true };
|
|
26984
26913
|
}
|
|
26985
26914
|
console.log(` building: ${pkg.name} (${client.name})`);
|
|
26986
26915
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
26987
|
-
const
|
|
26988
|
-
const
|
|
26916
|
+
const allDeps = pkg.packageJson.dependencies ?? {};
|
|
26917
|
+
const npmDeps = Object.entries(allDeps).filter(([, spec]) => !spec.startsWith("workspace:")).map(([name]) => name);
|
|
26918
|
+
const externals = [...peerDeps, ...npmDeps];
|
|
26989
26919
|
const result = await Bun.build({
|
|
26990
26920
|
entrypoints: [pkg.entrypoint],
|
|
26991
|
-
outdir:
|
|
26921
|
+
outdir: join8(outDir, "main"),
|
|
26992
26922
|
target: client.target,
|
|
26993
26923
|
format: "esm",
|
|
26994
26924
|
naming: "index.[ext]",
|
|
26995
26925
|
external: externals,
|
|
26926
|
+
plugins: [jsxDevShimPlugin()],
|
|
26996
26927
|
define: client.defines
|
|
26997
26928
|
});
|
|
26998
26929
|
if (!result.success) {
|
|
@@ -27025,115 +26956,142 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
|
|
|
27025
26956
|
}
|
|
27026
26957
|
return { declarationErrors };
|
|
27027
26958
|
}
|
|
27028
|
-
async function
|
|
27029
|
-
|
|
27030
|
-
const
|
|
27031
|
-
const
|
|
27032
|
-
|
|
27033
|
-
|
|
27034
|
-
|
|
26959
|
+
async function buildModulesByChunks(rootDir, outDir, plan, cache, noCache) {
|
|
26960
|
+
mkdirSync6(outDir, { recursive: true });
|
|
26961
|
+
const i18nCollector = new Map;
|
|
26962
|
+
const aggregateModules = [];
|
|
26963
|
+
let allCached = true;
|
|
26964
|
+
for (const chunk of plan.chunks) {
|
|
26965
|
+
const members = plan.groups.get(chunk) ?? [];
|
|
26966
|
+
if (members.length === 0)
|
|
26967
|
+
continue;
|
|
26968
|
+
const chunkOutDir = join8(outDir, chunk);
|
|
26969
|
+
mkdirSync6(chunkOutDir, { recursive: true });
|
|
26970
|
+
const result = await buildChunkGroup(rootDir, chunkOutDir, chunk, members, cache, noCache, i18nCollector);
|
|
26971
|
+
aggregateModules.push(...result.modules);
|
|
26972
|
+
if (!result.cached)
|
|
26973
|
+
allCached = false;
|
|
26974
|
+
}
|
|
26975
|
+
await finalizeTranslations(rootDir, join8(outDir, ".."), i18nCollector);
|
|
26976
|
+
return { modules: aggregateModules, cached: allCached };
|
|
26977
|
+
}
|
|
26978
|
+
async function buildChunkGroup(rootDir, chunkOutDir, chunk, members, cache, noCache, i18nCollector) {
|
|
26979
|
+
const unitId = `modules-chunk:${chunk}`;
|
|
26980
|
+
const pkgHashes = members.map((m) => ({
|
|
26981
|
+
name: m.pkg.name,
|
|
26982
|
+
safeName: m.safeName,
|
|
26983
|
+
moduleName: m.moduleName,
|
|
26984
|
+
srcHash: pkgSourceHash(m.pkg)
|
|
27035
26985
|
}));
|
|
27036
26986
|
const inputHash = sha256OfJson({
|
|
26987
|
+
chunk,
|
|
27037
26988
|
pkgHashes,
|
|
27038
26989
|
externals: SHELL_EXTERNALS,
|
|
27039
26990
|
define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" }
|
|
27040
26991
|
});
|
|
27041
26992
|
if (!noCache && isCacheHit(cache, unitId, inputHash)) {
|
|
27042
26993
|
const existing = cache.units[unitId]?.outputHashes ?? {};
|
|
27043
|
-
const
|
|
27044
|
-
|
|
27045
|
-
|
|
27046
|
-
const
|
|
27047
|
-
|
|
27048
|
-
|
|
27049
|
-
|
|
27050
|
-
|
|
27051
|
-
modules.push({ file, name, hash: existing[safeName] ?? sha256Hex(readFileSync8(filePath)) });
|
|
27052
|
-
}
|
|
27053
|
-
console.log(` \u2713 cached: modules-bundle (${modules.length} module(s))`);
|
|
27054
|
-
return { modules, cached: true };
|
|
27055
|
-
}
|
|
27056
|
-
return await actuallyBuild();
|
|
27057
|
-
async function actuallyBuild() {
|
|
27058
|
-
console.log(` building: modules-bundle (${packages.length} package(s))`);
|
|
27059
|
-
const tmpDir = join9(outDir, "_entries");
|
|
27060
|
-
mkdirSync7(tmpDir, { recursive: true });
|
|
27061
|
-
const entrypoints = [];
|
|
27062
|
-
const fileToName = new Map;
|
|
27063
|
-
for (const pkg of packages) {
|
|
27064
|
-
const safeName = basename2(pkg.path);
|
|
27065
|
-
const moduleName = pkg.name.includes("/") ? pkg.name.split("/").pop() : pkg.name;
|
|
27066
|
-
fileToName.set(safeName, moduleName);
|
|
27067
|
-
const wrapperFile = join9(tmpDir, `${safeName}.ts`);
|
|
27068
|
-
writeFileSync7(wrapperFile, `export * from "${pkg.name}";
|
|
27069
|
-
`);
|
|
27070
|
-
entrypoints.push(wrapperFile);
|
|
27071
|
-
}
|
|
27072
|
-
const i18nCollector = new Map;
|
|
27073
|
-
const arcExternalPlugin = {
|
|
27074
|
-
name: "arc-external",
|
|
27075
|
-
setup(build2) {
|
|
27076
|
-
build2.onResolve({ filter: /^@arcote\.tech\// }, (args) => {
|
|
27077
|
-
return { path: args.path, external: true };
|
|
27078
|
-
});
|
|
27079
|
-
}
|
|
27080
|
-
};
|
|
27081
|
-
const result = await Bun.build({
|
|
27082
|
-
entrypoints,
|
|
27083
|
-
outdir: outDir,
|
|
27084
|
-
splitting: true,
|
|
27085
|
-
format: "esm",
|
|
27086
|
-
target: "browser",
|
|
27087
|
-
external: SHELL_EXTERNALS,
|
|
27088
|
-
plugins: [arcExternalPlugin, i18nExtractPlugin(i18nCollector, rootDir)],
|
|
27089
|
-
naming: "[name].[ext]",
|
|
27090
|
-
define: {
|
|
27091
|
-
ONLY_SERVER: "false",
|
|
27092
|
-
ONLY_BROWSER: "true",
|
|
27093
|
-
ONLY_CLIENT: "true"
|
|
26994
|
+
const modules2 = [];
|
|
26995
|
+
let missing = false;
|
|
26996
|
+
for (const h of pkgHashes) {
|
|
26997
|
+
const file = `${h.safeName}.js`;
|
|
26998
|
+
const filePath = join8(chunkOutDir, file);
|
|
26999
|
+
if (!existsSync7(filePath)) {
|
|
27000
|
+
missing = true;
|
|
27001
|
+
break;
|
|
27094
27002
|
}
|
|
27095
|
-
|
|
27096
|
-
if (!result.success) {
|
|
27097
|
-
console.error("Modules bundle build failed:");
|
|
27098
|
-
for (const log2 of result.logs)
|
|
27099
|
-
console.error(log2);
|
|
27100
|
-
throw new Error("Module build failed");
|
|
27101
|
-
}
|
|
27102
|
-
await finalizeTranslations(rootDir, join9(outDir, ".."), i18nCollector);
|
|
27103
|
-
rmSync2(tmpDir, { recursive: true, force: true });
|
|
27104
|
-
const outputHashes = {};
|
|
27105
|
-
const modules = result.outputs.filter((o) => o.kind === "entry-point").map((o) => {
|
|
27106
|
-
const file = basename2(o.path);
|
|
27107
|
-
const safeName = file.replace(/\.js$/, "");
|
|
27108
|
-
const bytes = readFileSync8(o.path);
|
|
27109
|
-
const hash = sha256Hex(bytes);
|
|
27110
|
-
outputHashes[safeName] = hash;
|
|
27111
|
-
return {
|
|
27003
|
+
modules2.push({
|
|
27112
27004
|
file,
|
|
27113
|
-
name:
|
|
27114
|
-
|
|
27115
|
-
|
|
27116
|
-
|
|
27117
|
-
|
|
27118
|
-
|
|
27005
|
+
name: h.moduleName,
|
|
27006
|
+
chunk,
|
|
27007
|
+
hash: existing[h.safeName] ?? sha256Hex(readFileSync7(filePath))
|
|
27008
|
+
});
|
|
27009
|
+
}
|
|
27010
|
+
if (!missing) {
|
|
27011
|
+
console.log(` \u2713 cached: ${unitId} (${modules2.length} module(s))`);
|
|
27012
|
+
return { modules: modules2, cached: true };
|
|
27013
|
+
}
|
|
27014
|
+
console.log(` rebuilding ${unitId}: output file missing`);
|
|
27015
|
+
}
|
|
27016
|
+
console.log(` building: ${unitId} (${members.length} module(s))`);
|
|
27017
|
+
const tmpDir = join8(chunkOutDir, "_entries");
|
|
27018
|
+
mkdirSync6(tmpDir, { recursive: true });
|
|
27019
|
+
const entrypoints = [];
|
|
27020
|
+
const fileToModuleName = new Map;
|
|
27021
|
+
for (const m of members) {
|
|
27022
|
+
fileToModuleName.set(m.safeName, m.moduleName);
|
|
27023
|
+
const wrapperFile = join8(tmpDir, `${m.safeName}.ts`);
|
|
27024
|
+
writeFileSync6(wrapperFile, `export * from "${m.pkg.name}";
|
|
27025
|
+
`);
|
|
27026
|
+
entrypoints.push(wrapperFile);
|
|
27119
27027
|
}
|
|
27028
|
+
const arcExternalPlugin = {
|
|
27029
|
+
name: "arc-external",
|
|
27030
|
+
setup(build2) {
|
|
27031
|
+
build2.onResolve({ filter: /^@arcote\.tech\// }, (args) => {
|
|
27032
|
+
return { path: args.path, external: true };
|
|
27033
|
+
});
|
|
27034
|
+
}
|
|
27035
|
+
};
|
|
27036
|
+
const result = await Bun.build({
|
|
27037
|
+
entrypoints,
|
|
27038
|
+
outdir: chunkOutDir,
|
|
27039
|
+
splitting: true,
|
|
27040
|
+
format: "esm",
|
|
27041
|
+
target: "browser",
|
|
27042
|
+
external: [...SHELL_EXTERNALS],
|
|
27043
|
+
plugins: [
|
|
27044
|
+
arcExternalPlugin,
|
|
27045
|
+
jsxDevShimPlugin(),
|
|
27046
|
+
i18nExtractPlugin(i18nCollector, rootDir)
|
|
27047
|
+
],
|
|
27048
|
+
naming: "[name].[ext]",
|
|
27049
|
+
define: {
|
|
27050
|
+
ONLY_SERVER: "false",
|
|
27051
|
+
ONLY_BROWSER: "true",
|
|
27052
|
+
ONLY_CLIENT: "true"
|
|
27053
|
+
}
|
|
27054
|
+
});
|
|
27055
|
+
if (!result.success) {
|
|
27056
|
+
console.error(`Chunk "${chunk}" build failed:`);
|
|
27057
|
+
for (const log2 of result.logs)
|
|
27058
|
+
console.error(log2);
|
|
27059
|
+
throw new Error(`Module chunk build failed: ${chunk}`);
|
|
27060
|
+
}
|
|
27061
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
27062
|
+
const outputHashes = {};
|
|
27063
|
+
const modules = result.outputs.filter((o) => o.kind === "entry-point").map((o) => {
|
|
27064
|
+
const file = basename2(o.path);
|
|
27065
|
+
const safeName = file.replace(/\.js$/, "");
|
|
27066
|
+
const bytes = readFileSync7(o.path);
|
|
27067
|
+
const hash = sha256Hex(bytes);
|
|
27068
|
+
outputHashes[safeName] = hash;
|
|
27069
|
+
return {
|
|
27070
|
+
file,
|
|
27071
|
+
name: fileToModuleName.get(safeName) ?? safeName,
|
|
27072
|
+
chunk,
|
|
27073
|
+
hash
|
|
27074
|
+
};
|
|
27075
|
+
});
|
|
27076
|
+
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
27077
|
+
return { modules, cached: false };
|
|
27120
27078
|
}
|
|
27121
27079
|
async function buildTranslations(rootDir, arcDir, cache, noCache) {
|
|
27122
|
-
const localesDir =
|
|
27123
|
-
if (!
|
|
27080
|
+
const localesDir = join8(rootDir, "locales");
|
|
27081
|
+
if (!existsSync7(localesDir))
|
|
27124
27082
|
return;
|
|
27125
27083
|
const unitId = "translations";
|
|
27126
|
-
const poFiles =
|
|
27084
|
+
const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) => join8(localesDir, f));
|
|
27127
27085
|
if (poFiles.length === 0)
|
|
27128
27086
|
return;
|
|
27129
27087
|
const inputHash = sha256OfFiles(poFiles);
|
|
27130
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
27088
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(arcDir, "locales")])) {
|
|
27131
27089
|
console.log(` \u2713 cached: translations`);
|
|
27132
27090
|
return;
|
|
27133
27091
|
}
|
|
27134
27092
|
console.log(` building: translations (${poFiles.length} catalog(s))`);
|
|
27135
|
-
compileAllCatalogs(localesDir,
|
|
27136
|
-
const jsonFiles =
|
|
27093
|
+
compileAllCatalogs(localesDir, join8(arcDir, "locales"));
|
|
27094
|
+
const jsonFiles = readdirSync4(join8(arcDir, "locales")).filter((f) => f.endsWith(".json")).map((f) => join8(arcDir, "locales", f));
|
|
27137
27095
|
const outputHash = sha256OfFiles(jsonFiles);
|
|
27138
27096
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
27139
27097
|
}
|
|
@@ -27197,10 +27155,10 @@ var TAILWIND_INPUT_TEMPLATE = (rootRel) => `@import "tailwindcss";
|
|
|
27197
27155
|
}
|
|
27198
27156
|
`;
|
|
27199
27157
|
async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache) {
|
|
27200
|
-
|
|
27201
|
-
const inputCss =
|
|
27202
|
-
const outputCss =
|
|
27203
|
-
const themeOutput =
|
|
27158
|
+
mkdirSync6(arcDir, { recursive: true });
|
|
27159
|
+
const inputCss = join8(arcDir, "_input.css");
|
|
27160
|
+
const outputCss = join8(arcDir, "styles.css");
|
|
27161
|
+
const themeOutput = join8(arcDir, "theme.css");
|
|
27204
27162
|
const rootRel = relative3(arcDir, rootDir).replace(/\\/g, "/");
|
|
27205
27163
|
const inputCssContent = TAILWIND_INPUT_TEMPLATE(rootRel);
|
|
27206
27164
|
const tsxFilter = (rel) => {
|
|
@@ -27212,15 +27170,15 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
27212
27170
|
};
|
|
27213
27171
|
const wsHashes = {};
|
|
27214
27172
|
for (const p of packages) {
|
|
27215
|
-
wsHashes[p.name] = sha256OfDir(
|
|
27173
|
+
wsHashes[p.name] = sha256OfDir(join8(p.path, "src"), tsxFilter);
|
|
27216
27174
|
}
|
|
27217
|
-
const platformSrc =
|
|
27218
|
-
const arcDsSrc =
|
|
27175
|
+
const platformSrc = join8(rootDir, "node_modules", "@arcote.tech", "platform", "src");
|
|
27176
|
+
const arcDsSrc = join8(rootDir, "node_modules", "@arcote.tech", "arc-ds", "src");
|
|
27219
27177
|
const frameworkHashes = {
|
|
27220
27178
|
platform: sha256OfDir(platformSrc, tsxFilter),
|
|
27221
27179
|
arcDs: sha256OfDir(arcDsSrc, tsxFilter)
|
|
27222
27180
|
};
|
|
27223
|
-
const themeContent = themePath &&
|
|
27181
|
+
const themeContent = themePath && existsSync7(join8(rootDir, themePath)) ? readFileSync7(join8(rootDir, themePath)) : null;
|
|
27224
27182
|
const themeHash = themeContent ? sha256Hex(themeContent) : null;
|
|
27225
27183
|
const unitId = "styles";
|
|
27226
27184
|
const inputHash = sha256OfJson({
|
|
@@ -27237,146 +27195,57 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
27237
27195
|
return;
|
|
27238
27196
|
}
|
|
27239
27197
|
console.log(` building: styles`);
|
|
27240
|
-
|
|
27198
|
+
writeFileSync6(inputCss, inputCssContent);
|
|
27241
27199
|
execSync(`bunx @tailwindcss/cli -i ${inputCss} -o ${outputCss} --minify`, {
|
|
27242
27200
|
cwd: rootDir,
|
|
27243
27201
|
stdio: "inherit"
|
|
27244
27202
|
});
|
|
27245
27203
|
if (themePath && themeContent) {
|
|
27246
|
-
|
|
27204
|
+
writeFileSync6(themeOutput, themeContent);
|
|
27247
27205
|
}
|
|
27248
27206
|
const outFiles = [outputCss];
|
|
27249
|
-
if (themePath &&
|
|
27207
|
+
if (themePath && existsSync7(themeOutput))
|
|
27250
27208
|
outFiles.push(themeOutput);
|
|
27251
27209
|
const outputHash = sha256OfFiles(outFiles);
|
|
27252
27210
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
27253
27211
|
}
|
|
27254
27212
|
|
|
27255
|
-
// src/builder/dependency-collector.ts
|
|
27256
|
-
import { createHash } from "crypto";
|
|
27257
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
27258
|
-
import { basename as basename3, join as join10 } from "path";
|
|
27259
|
-
var FRAMEWORK_PEERS = [
|
|
27260
|
-
"@arcote.tech/arc",
|
|
27261
|
-
"@arcote.tech/arc-ds",
|
|
27262
|
-
"@arcote.tech/arc-react",
|
|
27263
|
-
"@arcote.tech/platform",
|
|
27264
|
-
"react",
|
|
27265
|
-
"react-dom"
|
|
27266
|
-
];
|
|
27267
|
-
function collectFrameworkDeps(arcDir, rootDir, packages) {
|
|
27268
|
-
mkdirSync8(arcDir, { recursive: true });
|
|
27269
|
-
const versions = resolveFrameworkVersions(rootDir, packages);
|
|
27270
|
-
const manifest = {
|
|
27271
|
-
name: "arc-platform-framework",
|
|
27272
|
-
private: true,
|
|
27273
|
-
type: "module",
|
|
27274
|
-
dependencies: versions
|
|
27275
|
-
};
|
|
27276
|
-
const manifestPath = join10(arcDir, "package.json");
|
|
27277
|
-
writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
27278
|
-
`);
|
|
27279
|
-
const targetLock = join10(arcDir, "bun.lock");
|
|
27280
|
-
writeFileSync8(targetLock, "");
|
|
27281
|
-
const hash = sha256ConcatHex([manifestPath, targetLock]);
|
|
27282
|
-
writeFileSync8(join10(arcDir, ".deps-hash"), hash + `
|
|
27283
|
-
`);
|
|
27284
|
-
return { hash, manifestPath };
|
|
27285
|
-
}
|
|
27286
|
-
function sha256ConcatHex(paths) {
|
|
27287
|
-
const hash = createHash("sha256");
|
|
27288
|
-
for (const p of paths) {
|
|
27289
|
-
if (existsSync9(p)) {
|
|
27290
|
-
hash.update(readFileSync9(p));
|
|
27291
|
-
}
|
|
27292
|
-
}
|
|
27293
|
-
return hash.digest("hex");
|
|
27294
|
-
}
|
|
27295
|
-
function collectModuleDeps(arcDir, pkg) {
|
|
27296
|
-
const safeName = basename3(pkg.path);
|
|
27297
|
-
const moduleDir = join10(arcDir, "modules", safeName);
|
|
27298
|
-
mkdirSync8(moduleDir, { recursive: true });
|
|
27299
|
-
const pkgDeps = pkg.packageJson.dependencies ?? {};
|
|
27300
|
-
const filtered = {};
|
|
27301
|
-
const frameworkSet = new Set(FRAMEWORK_PEERS);
|
|
27302
|
-
for (const [name, spec] of Object.entries(pkgDeps)) {
|
|
27303
|
-
if (frameworkSet.has(name))
|
|
27304
|
-
continue;
|
|
27305
|
-
if (spec.startsWith("workspace:"))
|
|
27306
|
-
continue;
|
|
27307
|
-
filtered[name] = spec;
|
|
27308
|
-
}
|
|
27309
|
-
const manifest = {
|
|
27310
|
-
name: `arc-module-${safeName}`,
|
|
27311
|
-
private: true,
|
|
27312
|
-
type: "module",
|
|
27313
|
-
dependencies: filtered
|
|
27314
|
-
};
|
|
27315
|
-
const manifestPath = join10(moduleDir, "package.json");
|
|
27316
|
-
writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
27317
|
-
`);
|
|
27318
|
-
const hash = sha256OfFiles2([manifestPath]);
|
|
27319
|
-
writeFileSync8(join10(moduleDir, ".deps-hash"), hash + `
|
|
27320
|
-
`);
|
|
27321
|
-
return { hash, manifestPath };
|
|
27322
|
-
}
|
|
27323
|
-
function resolveFrameworkVersions(rootDir, packages) {
|
|
27324
|
-
const rootPkg = JSON.parse(readFileSync9(join10(rootDir, "package.json"), "utf-8"));
|
|
27325
|
-
const rootDeps = rootPkg.dependencies ?? {};
|
|
27326
|
-
const out = {};
|
|
27327
|
-
for (const name of FRAMEWORK_PEERS) {
|
|
27328
|
-
const rootSpec = rootDeps[name];
|
|
27329
|
-
if (rootSpec) {
|
|
27330
|
-
out[name] = rootSpec;
|
|
27331
|
-
continue;
|
|
27332
|
-
}
|
|
27333
|
-
let found;
|
|
27334
|
-
for (const pkg of packages) {
|
|
27335
|
-
const spec = (pkg.packageJson.dependencies ?? {})[name];
|
|
27336
|
-
if (spec) {
|
|
27337
|
-
found = spec;
|
|
27338
|
-
break;
|
|
27339
|
-
}
|
|
27340
|
-
}
|
|
27341
|
-
out[name] = found ?? "*";
|
|
27342
|
-
}
|
|
27343
|
-
return out;
|
|
27344
|
-
}
|
|
27345
|
-
function sha256OfFiles2(paths) {
|
|
27346
|
-
const hash = createHash("sha256");
|
|
27347
|
-
for (const p of paths.sort()) {
|
|
27348
|
-
if (!existsSync9(p))
|
|
27349
|
-
continue;
|
|
27350
|
-
hash.update(p);
|
|
27351
|
-
hash.update("\x00");
|
|
27352
|
-
hash.update(readFileSync9(p));
|
|
27353
|
-
hash.update("\x00");
|
|
27354
|
-
}
|
|
27355
|
-
return hash.digest("hex");
|
|
27356
|
-
}
|
|
27357
|
-
|
|
27358
27213
|
// src/builder/access-extractor.ts
|
|
27359
27214
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
27360
|
-
import {
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
|
|
27368
|
-
|
|
27369
|
-
|
|
27370
|
-
|
|
27371
|
-
const
|
|
27372
|
-
|
|
27215
|
+
import {
|
|
27216
|
+
existsSync as existsSync8,
|
|
27217
|
+
mkdirSync as mkdirSync7,
|
|
27218
|
+
readFileSync as readFileSync8,
|
|
27219
|
+
realpathSync as realpathSync2,
|
|
27220
|
+
unlinkSync as unlinkSync2,
|
|
27221
|
+
writeFileSync as writeFileSync7
|
|
27222
|
+
} from "fs";
|
|
27223
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
27224
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
27225
|
+
function locatePlatformServerEntry() {
|
|
27226
|
+
const here = fileURLToPath5(import.meta.url);
|
|
27227
|
+
const arcRoot = realpathSync2(dirname6(dirname6(dirname6(dirname6(here)))));
|
|
27228
|
+
return join9(arcRoot, "packages", "platform", "src", "index.server.ts");
|
|
27229
|
+
}
|
|
27230
|
+
async function extractAccessMap(rootDir, packages) {
|
|
27231
|
+
const serverBundles = packages.filter((p) => isContextPackage(p.packageJson)).map((p) => ({
|
|
27232
|
+
name: p.name,
|
|
27233
|
+
path: join9(p.path, "dist", "server", "main", "index.js")
|
|
27234
|
+
})).filter((b) => existsSync8(b.path));
|
|
27235
|
+
const workerDir = join9(rootDir, ".arc", ".tmp");
|
|
27236
|
+
mkdirSync7(workerDir, { recursive: true });
|
|
27237
|
+
const workerPath = join9(workerDir, `access-extractor-${Date.now()}.mjs`);
|
|
27238
|
+
const outPath = join9(workerDir, `access-${Date.now()}.json`);
|
|
27239
|
+
writeFileSync7(workerPath, WORKER_SOURCE);
|
|
27373
27240
|
try {
|
|
27374
27241
|
const proc2 = spawn2({
|
|
27375
27242
|
cmd: ["bun", "run", workerPath],
|
|
27243
|
+
cwd: rootDir,
|
|
27376
27244
|
env: {
|
|
27377
27245
|
...process.env,
|
|
27378
27246
|
ARC_ACCESS_BUNDLES: JSON.stringify(serverBundles),
|
|
27379
|
-
ARC_ACCESS_OUT: outPath
|
|
27247
|
+
ARC_ACCESS_OUT: outPath,
|
|
27248
|
+
ARC_PLATFORM_ENTRY: locatePlatformServerEntry()
|
|
27380
27249
|
},
|
|
27381
27250
|
stdout: "pipe",
|
|
27382
27251
|
stderr: "inherit"
|
|
@@ -27385,17 +27254,20 @@ async function extractAccessMap(arcDir, packages) {
|
|
|
27385
27254
|
if (exit !== 0) {
|
|
27386
27255
|
throw new Error(`access-extractor subprocess exited with ${exit}`);
|
|
27387
27256
|
}
|
|
27388
|
-
return JSON.parse(
|
|
27257
|
+
return JSON.parse(readFileSync8(outPath, "utf-8"));
|
|
27389
27258
|
} finally {
|
|
27390
27259
|
try {
|
|
27391
27260
|
unlinkSync2(workerPath);
|
|
27392
27261
|
} catch {}
|
|
27262
|
+
try {
|
|
27263
|
+
unlinkSync2(outPath);
|
|
27264
|
+
} catch {}
|
|
27393
27265
|
}
|
|
27394
27266
|
}
|
|
27395
27267
|
var WORKER_SOURCE = `
|
|
27396
|
-
import { existsSync } from "node:fs";
|
|
27397
|
-
|
|
27398
27268
|
globalThis.ONLY_SERVER = true;
|
|
27269
|
+
globalThis.ONLY_BROWSER = false;
|
|
27270
|
+
globalThis.ONLY_CLIENT = false;
|
|
27399
27271
|
|
|
27400
27272
|
const bundles = JSON.parse(process.env.ARC_ACCESS_BUNDLES || "[]");
|
|
27401
27273
|
const out = process.env.ARC_ACCESS_OUT;
|
|
@@ -27404,20 +27276,16 @@ if (!out) {
|
|
|
27404
27276
|
process.exit(2);
|
|
27405
27277
|
}
|
|
27406
27278
|
|
|
27407
|
-
|
|
27279
|
+
// Direct file-path import \u2014 bypasses node_modules resolution entirely.
|
|
27280
|
+
// Avoids quirks with bun-link snapshots holding stale package.json exports.
|
|
27281
|
+
const platform = await import(process.env.ARC_PLATFORM_ENTRY);
|
|
27408
27282
|
|
|
27409
|
-
for (const { name, path
|
|
27410
|
-
const target = existsSync(path) ? path : fallback;
|
|
27411
|
-
if (!target || !existsSync(target)) {
|
|
27412
|
-
// No server bundle on either path \u2014 module has no protected access rules
|
|
27413
|
-
// to discover. Skip silently rather than logging a misleading error.
|
|
27414
|
-
continue;
|
|
27415
|
-
}
|
|
27283
|
+
for (const { name, path } of bundles) {
|
|
27416
27284
|
try {
|
|
27417
|
-
await import(
|
|
27285
|
+
await import(path);
|
|
27418
27286
|
} catch (e) {
|
|
27419
|
-
console.error("[access-extractor-worker] failed to import", name, "
|
|
27420
|
-
//
|
|
27287
|
+
console.error("[access-extractor-worker] failed to import", name, ":", e.message);
|
|
27288
|
+
// Partial map is better than total failure.
|
|
27421
27289
|
}
|
|
27422
27290
|
}
|
|
27423
27291
|
|
|
@@ -27435,6 +27303,123 @@ const { writeFileSync } = await import("node:fs");
|
|
|
27435
27303
|
writeFileSync(out, JSON.stringify(result, null, 2) + "\\n");
|
|
27436
27304
|
`.trim();
|
|
27437
27305
|
|
|
27306
|
+
// src/builder/chunk-planner.ts
|
|
27307
|
+
import { basename as basename3 } from "path";
|
|
27308
|
+
var PUBLIC_CHUNK = "public";
|
|
27309
|
+
function planChunks(packages, accessMap) {
|
|
27310
|
+
const assignments = new Map;
|
|
27311
|
+
const groups = new Map;
|
|
27312
|
+
for (const pkg of packages) {
|
|
27313
|
+
const moduleName = moduleNameOf(pkg.name);
|
|
27314
|
+
const access = accessMap[moduleName];
|
|
27315
|
+
const chunk = resolveChunk(moduleName, access);
|
|
27316
|
+
const entry = {
|
|
27317
|
+
pkg,
|
|
27318
|
+
chunk,
|
|
27319
|
+
moduleName,
|
|
27320
|
+
safeName: basename3(pkg.path)
|
|
27321
|
+
};
|
|
27322
|
+
assignments.set(moduleName, entry);
|
|
27323
|
+
const bucket = groups.get(chunk);
|
|
27324
|
+
if (bucket) {
|
|
27325
|
+
bucket.push(entry);
|
|
27326
|
+
} else {
|
|
27327
|
+
groups.set(chunk, [entry]);
|
|
27328
|
+
}
|
|
27329
|
+
}
|
|
27330
|
+
const chunks = [...groups.keys()].sort((a, b) => {
|
|
27331
|
+
if (a === PUBLIC_CHUNK)
|
|
27332
|
+
return -1;
|
|
27333
|
+
if (b === PUBLIC_CHUNK)
|
|
27334
|
+
return 1;
|
|
27335
|
+
return a.localeCompare(b);
|
|
27336
|
+
});
|
|
27337
|
+
return { assignments, groups, chunks };
|
|
27338
|
+
}
|
|
27339
|
+
function moduleNameOf(pkgName) {
|
|
27340
|
+
return pkgName.includes("/") ? pkgName.split("/").pop() : pkgName;
|
|
27341
|
+
}
|
|
27342
|
+
function resolveChunk(moduleName, access) {
|
|
27343
|
+
if (!access || access.rules.length === 0)
|
|
27344
|
+
return PUBLIC_CHUNK;
|
|
27345
|
+
const tokenNames = new Set(access.rules.map((r) => r.token.name));
|
|
27346
|
+
if (tokenNames.size === 1) {
|
|
27347
|
+
return [...tokenNames][0];
|
|
27348
|
+
}
|
|
27349
|
+
const list = [...tokenNames].sort().join(", ");
|
|
27350
|
+
throw new Error(`Module "${moduleName}" has access rules for multiple tokens [${list}]. ` + `Multi-token modules are not supported \u2014 split the module or unify on a single token type.`);
|
|
27351
|
+
}
|
|
27352
|
+
|
|
27353
|
+
// src/builder/dependency-collector.ts
|
|
27354
|
+
import { createHash } from "crypto";
|
|
27355
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
27356
|
+
import { basename as basename4, join as join10 } from "path";
|
|
27357
|
+
function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
|
|
27358
|
+
mkdirSync8(arcDir, { recursive: true });
|
|
27359
|
+
const versions = resolveFrameworkVersions(rootDir, packages);
|
|
27360
|
+
for (const { name, version } of sharedDeps) {
|
|
27361
|
+
versions[name] = version;
|
|
27362
|
+
}
|
|
27363
|
+
const manifest = {
|
|
27364
|
+
name: "arc-platform-framework",
|
|
27365
|
+
private: true,
|
|
27366
|
+
type: "module",
|
|
27367
|
+
dependencies: versions
|
|
27368
|
+
};
|
|
27369
|
+
const manifestPath = join10(arcDir, "package.json");
|
|
27370
|
+
writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
27371
|
+
`);
|
|
27372
|
+
const hash = sha256Hex2(readFileSync9(manifestPath));
|
|
27373
|
+
writeFileSync8(join10(arcDir, ".deps-hash"), hash + `
|
|
27374
|
+
`);
|
|
27375
|
+
return { hash, manifestPath };
|
|
27376
|
+
}
|
|
27377
|
+
function sha256Hex2(bytes) {
|
|
27378
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
27379
|
+
}
|
|
27380
|
+
function resolveFrameworkVersions(rootDir, packages) {
|
|
27381
|
+
const rootPkg = JSON.parse(readFileSync9(join10(rootDir, "package.json"), "utf-8"));
|
|
27382
|
+
const rootDeps = rootPkg.dependencies ?? {};
|
|
27383
|
+
const rootDevDeps = rootPkg.devDependencies ?? {};
|
|
27384
|
+
const required = new Set(FRAMEWORK_PEERS);
|
|
27385
|
+
for (const pkg of packages) {
|
|
27386
|
+
const peers = pkg.packageJson.peerDependencies ?? {};
|
|
27387
|
+
const deps = pkg.packageJson.dependencies ?? {};
|
|
27388
|
+
for (const name of Object.keys(peers)) {
|
|
27389
|
+
if (name.startsWith("@arcote.tech/"))
|
|
27390
|
+
required.add(name);
|
|
27391
|
+
}
|
|
27392
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
27393
|
+
if (spec.startsWith("workspace:"))
|
|
27394
|
+
continue;
|
|
27395
|
+
if (name.startsWith("@arcote.tech/") || !isFrameworkExternal(name))
|
|
27396
|
+
continue;
|
|
27397
|
+
required.add(name);
|
|
27398
|
+
}
|
|
27399
|
+
}
|
|
27400
|
+
const out = {};
|
|
27401
|
+
for (const name of required) {
|
|
27402
|
+
const rootSpec = rootDeps[name] ?? rootDevDeps[name];
|
|
27403
|
+
if (rootSpec) {
|
|
27404
|
+
out[name] = rootSpec;
|
|
27405
|
+
continue;
|
|
27406
|
+
}
|
|
27407
|
+
let found;
|
|
27408
|
+
for (const pkg of packages) {
|
|
27409
|
+
const spec = (pkg.packageJson.dependencies ?? {})[name] ?? (pkg.packageJson.peerDependencies ?? {})[name];
|
|
27410
|
+
if (spec) {
|
|
27411
|
+
found = spec;
|
|
27412
|
+
break;
|
|
27413
|
+
}
|
|
27414
|
+
}
|
|
27415
|
+
out[name] = found ?? "*";
|
|
27416
|
+
}
|
|
27417
|
+
return out;
|
|
27418
|
+
}
|
|
27419
|
+
function isFrameworkExternal(_name) {
|
|
27420
|
+
return true;
|
|
27421
|
+
}
|
|
27422
|
+
|
|
27438
27423
|
// src/platform/shared.ts
|
|
27439
27424
|
var C = {
|
|
27440
27425
|
reset: "\x1B[0m",
|
|
@@ -27451,23 +27436,23 @@ function resolveWorkspace() {
|
|
|
27451
27436
|
err("No package.json found");
|
|
27452
27437
|
process.exit(1);
|
|
27453
27438
|
}
|
|
27454
|
-
const rootDir =
|
|
27455
|
-
const rootPkg = JSON.parse(
|
|
27439
|
+
const rootDir = dirname7(packageJsonPath);
|
|
27440
|
+
const rootPkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
27456
27441
|
const appName = rootPkg.name ?? "Arc App";
|
|
27457
|
-
const arcDir =
|
|
27442
|
+
const arcDir = join11(rootDir, ".arc", "platform");
|
|
27458
27443
|
log2("Scanning workspaces...");
|
|
27459
27444
|
const packages = discoverPackages(rootDir);
|
|
27460
|
-
|
|
27461
|
-
|
|
27462
|
-
|
|
27463
|
-
|
|
27445
|
+
if (packages.length > 0) {
|
|
27446
|
+
ok(`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`);
|
|
27447
|
+
} else {
|
|
27448
|
+
log2("No workspace packages found \u2014 assuming image runtime mode.");
|
|
27464
27449
|
}
|
|
27465
27450
|
let manifest;
|
|
27466
27451
|
for (const name of ["manifest.json", "manifest.webmanifest"]) {
|
|
27467
|
-
const manifestPath =
|
|
27452
|
+
const manifestPath = join11(rootDir, name);
|
|
27468
27453
|
if (existsSync10(manifestPath)) {
|
|
27469
27454
|
try {
|
|
27470
|
-
const data = JSON.parse(
|
|
27455
|
+
const data = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
27471
27456
|
const icons = data.icons;
|
|
27472
27457
|
manifest = {
|
|
27473
27458
|
path: manifestPath,
|
|
@@ -27486,10 +27471,10 @@ function resolveWorkspace() {
|
|
|
27486
27471
|
rootPkg,
|
|
27487
27472
|
appName,
|
|
27488
27473
|
arcDir,
|
|
27489
|
-
modulesDir:
|
|
27490
|
-
shellDir:
|
|
27491
|
-
assetsDir:
|
|
27492
|
-
publicDir:
|
|
27474
|
+
modulesDir: join11(arcDir, "modules"),
|
|
27475
|
+
shellDir: join11(arcDir, "shell"),
|
|
27476
|
+
assetsDir: join11(arcDir, "assets"),
|
|
27477
|
+
publicDir: join11(rootDir, "public"),
|
|
27493
27478
|
packages,
|
|
27494
27479
|
manifest
|
|
27495
27480
|
};
|
|
@@ -27499,29 +27484,28 @@ async function buildAll(ws, opts = {}) {
|
|
|
27499
27484
|
const noCache = opts.noCache ?? false;
|
|
27500
27485
|
const themePath = ws.rootPkg.arc?.theme;
|
|
27501
27486
|
log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
27487
|
+
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
27488
|
+
copyContextServerBundles(ws);
|
|
27489
|
+
const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
|
|
27490
|
+
mkdirSync9(ws.arcDir, { recursive: true });
|
|
27491
|
+
writeFileSync9(join11(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
|
|
27492
|
+
`);
|
|
27493
|
+
const plan = planChunks(ws.packages, accessMap);
|
|
27494
|
+
ok(`Chunks: ${plan.chunks.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`).join(", ")}`);
|
|
27495
|
+
const [modulesResult] = await Promise.all([
|
|
27496
|
+
buildModulesByChunks(ws.rootDir, ws.modulesDir, plan, cache, noCache),
|
|
27497
|
+
buildShell(ws, cache, noCache),
|
|
27506
27498
|
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
27507
27499
|
copyBrowserAssets(ws, cache, noCache),
|
|
27508
27500
|
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache)
|
|
27509
27501
|
]);
|
|
27510
|
-
saveBuildCache(ws.arcDir, cache);
|
|
27511
27502
|
collectFrameworkDeps(ws.arcDir, ws.rootDir, ws.packages);
|
|
27512
|
-
|
|
27513
|
-
|
|
27514
|
-
|
|
27515
|
-
try {
|
|
27516
|
-
await extractAccessMap(ws.arcDir, ws.packages);
|
|
27517
|
-
} catch (e) {
|
|
27518
|
-
err(`access-extractor failed: ${e.message}`);
|
|
27519
|
-
}
|
|
27520
|
-
const finalManifest = assembleManifest(ws, modulesResult.modules, cache);
|
|
27521
|
-
writeFileSync10(join12(ws.modulesDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
|
|
27503
|
+
saveBuildCache(ws.arcDir, cache);
|
|
27504
|
+
const finalManifest = assembleManifest(ws, modulesResult.modules, plan.chunks, cache);
|
|
27505
|
+
writeFileSync9(join11(ws.modulesDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
|
|
27522
27506
|
return finalManifest;
|
|
27523
27507
|
}
|
|
27524
|
-
function assembleManifest(ws, modules, cache) {
|
|
27508
|
+
function assembleManifest(ws, modules, chunks, cache) {
|
|
27525
27509
|
const shellEntries = {};
|
|
27526
27510
|
for (const [unitId, entry] of Object.entries(cache.units)) {
|
|
27527
27511
|
if (unitId.startsWith("shell:") && entry.outputHash) {
|
|
@@ -27532,30 +27516,47 @@ function assembleManifest(ws, modules, cache) {
|
|
|
27532
27516
|
const stylesHash = cache.units["styles"]?.outputHash ?? "";
|
|
27533
27517
|
return {
|
|
27534
27518
|
modules,
|
|
27519
|
+
chunks,
|
|
27535
27520
|
shellHash,
|
|
27536
27521
|
stylesHash,
|
|
27537
27522
|
buildTime: new Date().toISOString()
|
|
27538
27523
|
};
|
|
27539
27524
|
}
|
|
27525
|
+
function copyContextServerBundles(ws) {
|
|
27526
|
+
const outDir = join11(ws.arcDir, "server");
|
|
27527
|
+
mkdirSync9(outDir, { recursive: true });
|
|
27528
|
+
for (const pkg of ws.packages) {
|
|
27529
|
+
if (!isContextPackage(pkg.packageJson))
|
|
27530
|
+
continue;
|
|
27531
|
+
const src = join11(pkg.path, "dist", "server", "main", "index.js");
|
|
27532
|
+
if (!existsSync10(src)) {
|
|
27533
|
+
err(`Server bundle missing for ${pkg.name}: ${src}`);
|
|
27534
|
+
continue;
|
|
27535
|
+
}
|
|
27536
|
+
const safeName = pkg.path.split("/").pop();
|
|
27537
|
+
const dst = join11(outDir, `${safeName}.js`);
|
|
27538
|
+
copyFileSync(src, dst);
|
|
27539
|
+
}
|
|
27540
|
+
}
|
|
27540
27541
|
function resolveAssetSource(from, pkgDir, rootDir) {
|
|
27541
27542
|
if (from.startsWith("./") || from.startsWith("../")) {
|
|
27542
|
-
const resolved =
|
|
27543
|
+
const resolved = join11(pkgDir, from);
|
|
27543
27544
|
return existsSync10(resolved) ? resolved : null;
|
|
27544
27545
|
}
|
|
27545
27546
|
const candidates = [
|
|
27546
|
-
|
|
27547
|
-
|
|
27547
|
+
join11(rootDir, "node_modules", from),
|
|
27548
|
+
join11(pkgDir, "node_modules", from)
|
|
27548
27549
|
];
|
|
27549
27550
|
for (const c of candidates) {
|
|
27550
27551
|
if (existsSync10(c))
|
|
27551
27552
|
return c;
|
|
27552
27553
|
}
|
|
27553
|
-
const bunCacheDir =
|
|
27554
|
+
const bunCacheDir = join11(rootDir, "node_modules", ".bun");
|
|
27554
27555
|
if (existsSync10(bunCacheDir)) {
|
|
27555
|
-
for (const entry of
|
|
27556
|
+
for (const entry of readdirSync5(bunCacheDir, { withFileTypes: true })) {
|
|
27556
27557
|
if (!entry.isDirectory())
|
|
27557
27558
|
continue;
|
|
27558
|
-
const candidate =
|
|
27559
|
+
const candidate = join11(bunCacheDir, entry.name, "node_modules", from);
|
|
27559
27560
|
if (existsSync10(candidate))
|
|
27560
27561
|
return candidate;
|
|
27561
27562
|
}
|
|
@@ -27563,11 +27564,11 @@ function resolveAssetSource(from, pkgDir, rootDir) {
|
|
|
27563
27564
|
return null;
|
|
27564
27565
|
}
|
|
27565
27566
|
function readBrowserAssets(pkgDir) {
|
|
27566
|
-
const pkgJsonPath =
|
|
27567
|
+
const pkgJsonPath = join11(pkgDir, "package.json");
|
|
27567
27568
|
if (!existsSync10(pkgJsonPath))
|
|
27568
27569
|
return [];
|
|
27569
27570
|
try {
|
|
27570
|
-
const pkg = JSON.parse(
|
|
27571
|
+
const pkg = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
|
|
27571
27572
|
const assets = pkg.arc?.browserAssets;
|
|
27572
27573
|
if (!Array.isArray(assets))
|
|
27573
27574
|
return [];
|
|
@@ -27577,14 +27578,14 @@ function readBrowserAssets(pkgDir) {
|
|
|
27577
27578
|
}
|
|
27578
27579
|
}
|
|
27579
27580
|
function discoverBrowserAssets(ws) {
|
|
27580
|
-
const arcDir =
|
|
27581
|
+
const arcDir = join11(ws.rootDir, "node_modules", "@arcote.tech");
|
|
27581
27582
|
if (!existsSync10(arcDir))
|
|
27582
27583
|
return [];
|
|
27583
27584
|
const out = [];
|
|
27584
|
-
for (const entry of
|
|
27585
|
+
for (const entry of readdirSync5(arcDir, { withFileTypes: true })) {
|
|
27585
27586
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
27586
27587
|
continue;
|
|
27587
|
-
const pkgDir =
|
|
27588
|
+
const pkgDir = join11(arcDir, entry.name);
|
|
27588
27589
|
const assets = readBrowserAssets(pkgDir);
|
|
27589
27590
|
for (const asset of assets) {
|
|
27590
27591
|
const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
|
|
@@ -27598,7 +27599,7 @@ function discoverBrowserAssets(ws) {
|
|
|
27598
27599
|
return out;
|
|
27599
27600
|
}
|
|
27600
27601
|
async function copyBrowserAssets(ws, cache, noCache) {
|
|
27601
|
-
|
|
27602
|
+
mkdirSync9(ws.assetsDir, { recursive: true });
|
|
27602
27603
|
const assets = discoverBrowserAssets(ws);
|
|
27603
27604
|
if (assets.length === 0)
|
|
27604
27605
|
return;
|
|
@@ -27609,7 +27610,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
27609
27610
|
to: a.to,
|
|
27610
27611
|
mtime: mtimeOf(a.src)
|
|
27611
27612
|
})));
|
|
27612
|
-
const requiredOutputs = assets.map((a) =>
|
|
27613
|
+
const requiredOutputs = assets.map((a) => join11(ws.assetsDir, a.to));
|
|
27613
27614
|
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
27614
27615
|
console.log(` \u2713 cached: browser-assets (${assets.length})`);
|
|
27615
27616
|
return;
|
|
@@ -27617,10 +27618,10 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
27617
27618
|
console.log(` building: browser-assets (${assets.length})`);
|
|
27618
27619
|
const outputHashes = {};
|
|
27619
27620
|
for (const asset of assets) {
|
|
27620
|
-
const dest =
|
|
27621
|
-
|
|
27621
|
+
const dest = join11(ws.assetsDir, asset.to);
|
|
27622
|
+
mkdirSync9(dirname7(dest), { recursive: true });
|
|
27622
27623
|
copyFileSync(asset.src, dest);
|
|
27623
|
-
outputHashes[asset.to] = sha256Hex(
|
|
27624
|
+
outputHashes[asset.to] = sha256Hex(readFileSync10(dest));
|
|
27624
27625
|
}
|
|
27625
27626
|
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
27626
27627
|
}
|
|
@@ -27641,7 +27642,7 @@ function collectArcPeerDeps(packages) {
|
|
|
27641
27642
|
return [short, pkg];
|
|
27642
27643
|
});
|
|
27643
27644
|
}
|
|
27644
|
-
var
|
|
27645
|
+
var REACT_ENTRIES = [
|
|
27645
27646
|
[
|
|
27646
27647
|
"react",
|
|
27647
27648
|
`import React from "react";
|
|
@@ -27671,8 +27672,8 @@ export const { createPortal, flushSync } = ReactDOM;`
|
|
|
27671
27672
|
`export { createRoot, hydrateRoot } from "react-dom/client";`
|
|
27672
27673
|
]
|
|
27673
27674
|
];
|
|
27674
|
-
var REACT_OUTPUT_FILES =
|
|
27675
|
-
var
|
|
27675
|
+
var REACT_OUTPUT_FILES = REACT_ENTRIES.map(([n]) => `${n}.js`);
|
|
27676
|
+
var SHELL_BASE_EXTERNAL = [
|
|
27676
27677
|
"react",
|
|
27677
27678
|
"react-dom",
|
|
27678
27679
|
"react/jsx-runtime",
|
|
@@ -27689,27 +27690,27 @@ var sourceFilter2 = (rel) => {
|
|
|
27689
27690
|
return true;
|
|
27690
27691
|
};
|
|
27691
27692
|
function arcPkgSrcHash(rootDir, pkg) {
|
|
27692
|
-
const srcDir =
|
|
27693
|
+
const srcDir = join11(rootDir, "node_modules", pkg, "src");
|
|
27693
27694
|
if (existsSync10(srcDir))
|
|
27694
27695
|
return sha256OfDir(srcDir, sourceFilter2);
|
|
27695
|
-
return sha256OfDir(
|
|
27696
|
+
return sha256OfDir(join11(rootDir, "node_modules", pkg), sourceFilter2);
|
|
27696
27697
|
}
|
|
27697
27698
|
async function buildShellReact(shellDir, tmpDir, rootDir, cache, noCache) {
|
|
27698
27699
|
const unitId = "shell:react";
|
|
27699
27700
|
const inputHash = sha256OfJson({
|
|
27700
27701
|
react: readInstalledVersion(rootDir, "react"),
|
|
27701
27702
|
"react-dom": readInstalledVersion(rootDir, "react-dom"),
|
|
27702
|
-
entries:
|
|
27703
|
+
entries: REACT_ENTRIES.map(([k, v]) => [k, v])
|
|
27703
27704
|
});
|
|
27704
|
-
const requiredOutputs = REACT_OUTPUT_FILES.map((f) =>
|
|
27705
|
+
const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join11(shellDir, f));
|
|
27705
27706
|
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
27706
27707
|
console.log(` \u2713 cached: shell:react`);
|
|
27707
27708
|
return;
|
|
27708
27709
|
}
|
|
27709
27710
|
console.log(` building: shell:react`);
|
|
27710
27711
|
const reactEps = [];
|
|
27711
|
-
for (const [name, code] of
|
|
27712
|
-
const f =
|
|
27712
|
+
for (const [name, code] of REACT_ENTRIES) {
|
|
27713
|
+
const f = join11(tmpDir, `${name}.ts`);
|
|
27713
27714
|
await Bun.write(f, code);
|
|
27714
27715
|
reactEps.push(f);
|
|
27715
27716
|
}
|
|
@@ -27736,16 +27737,16 @@ async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir,
|
|
|
27736
27737
|
pkg,
|
|
27737
27738
|
version: readInstalledVersion(rootDir, pkg),
|
|
27738
27739
|
src: arcPkgSrcHash(rootDir, pkg),
|
|
27739
|
-
base:
|
|
27740
|
+
base: SHELL_BASE_EXTERNAL,
|
|
27740
27741
|
others: [...otherExternals].sort()
|
|
27741
27742
|
});
|
|
27742
|
-
const outputFile =
|
|
27743
|
+
const outputFile = join11(shellDir, `${shortName}.js`);
|
|
27743
27744
|
if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
|
|
27744
27745
|
console.log(` \u2713 cached: ${unitId}`);
|
|
27745
27746
|
return;
|
|
27746
27747
|
}
|
|
27747
27748
|
console.log(` building: ${unitId}`);
|
|
27748
|
-
const f =
|
|
27749
|
+
const f = join11(tmpDir, `${shortName}.ts`);
|
|
27749
27750
|
await Bun.write(f, `export * from "${pkg}";
|
|
27750
27751
|
`);
|
|
27751
27752
|
const r = await Bun.build({
|
|
@@ -27754,7 +27755,7 @@ async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir,
|
|
|
27754
27755
|
format: "esm",
|
|
27755
27756
|
target: "browser",
|
|
27756
27757
|
naming: "[name].[ext]",
|
|
27757
|
-
external: [...
|
|
27758
|
+
external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
|
|
27758
27759
|
define: {
|
|
27759
27760
|
ONLY_SERVER: "false",
|
|
27760
27761
|
ONLY_BROWSER: "true",
|
|
@@ -27769,10 +27770,10 @@ async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir,
|
|
|
27769
27770
|
const outputHash = sha256OfFiles([outputFile]);
|
|
27770
27771
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
27771
27772
|
}
|
|
27772
|
-
async function
|
|
27773
|
-
|
|
27774
|
-
const tmpDir =
|
|
27775
|
-
|
|
27773
|
+
async function buildShell(ws, cache, noCache) {
|
|
27774
|
+
mkdirSync9(ws.shellDir, { recursive: true });
|
|
27775
|
+
const tmpDir = join11(ws.shellDir, "_tmp");
|
|
27776
|
+
mkdirSync9(tmpDir, { recursive: true });
|
|
27776
27777
|
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
27777
27778
|
const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
|
|
27778
27779
|
const tasks = [
|
|
@@ -27780,36 +27781,43 @@ async function buildShell2(ws, cache, noCache) {
|
|
|
27780
27781
|
...arcEntries.map(([short, pkg]) => () => buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache))
|
|
27781
27782
|
];
|
|
27782
27783
|
await pAll(tasks);
|
|
27783
|
-
|
|
27784
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
27784
27785
|
}
|
|
27785
|
-
async function loadServerContext(
|
|
27786
|
-
const ctxPackages = packages.filter((p) => isContextPackage(p.packageJson));
|
|
27787
|
-
if (ctxPackages.length === 0)
|
|
27788
|
-
return { context: null, moduleAccess: new Map };
|
|
27786
|
+
async function loadServerContext(ws) {
|
|
27789
27787
|
globalThis.ONLY_SERVER = true;
|
|
27790
27788
|
globalThis.ONLY_BROWSER = false;
|
|
27791
27789
|
globalThis.ONLY_CLIENT = false;
|
|
27792
|
-
const platformDir =
|
|
27793
|
-
const platformPkg = JSON.parse(
|
|
27794
|
-
const platformEntry =
|
|
27790
|
+
const platformDir = join11(process.cwd(), "node_modules", "@arcote.tech", "platform");
|
|
27791
|
+
const platformPkg = JSON.parse(readFileSync10(join11(platformDir, "package.json"), "utf-8"));
|
|
27792
|
+
const platformEntry = join11(platformDir, platformPkg.main ?? "src/index.ts");
|
|
27795
27793
|
await import(platformEntry);
|
|
27796
|
-
|
|
27797
|
-
|
|
27798
|
-
|
|
27799
|
-
|
|
27800
|
-
|
|
27794
|
+
const serverDir = join11(ws.arcDir, "server");
|
|
27795
|
+
const bundles = existsSync10(serverDir) ? readdirSync5(serverDir).filter((f) => f.endsWith(".js")) : [];
|
|
27796
|
+
if (bundles.length > 0) {
|
|
27797
|
+
for (const file of bundles) {
|
|
27798
|
+
const bundlePath = join11(serverDir, file);
|
|
27799
|
+
try {
|
|
27800
|
+
await import(bundlePath);
|
|
27801
|
+
} catch (e) {
|
|
27802
|
+
err(`Failed to load server bundle ${file}: ${e}`);
|
|
27803
|
+
}
|
|
27801
27804
|
}
|
|
27802
|
-
|
|
27803
|
-
|
|
27804
|
-
|
|
27805
|
-
|
|
27805
|
+
} else if (ws.packages.length > 0) {
|
|
27806
|
+
const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
|
|
27807
|
+
for (const ctx of ctxPackages) {
|
|
27808
|
+
const serverDist = join11(ctx.path, "dist", "server", "main", "index.js");
|
|
27809
|
+
if (!existsSync10(serverDist)) {
|
|
27810
|
+
err(`Context server dist not found: ${serverDist}`);
|
|
27811
|
+
continue;
|
|
27812
|
+
}
|
|
27813
|
+
try {
|
|
27814
|
+
await import(serverDist);
|
|
27815
|
+
} catch (e) {
|
|
27816
|
+
err(`Failed to load server context from ${ctx.name}: ${e}`);
|
|
27817
|
+
}
|
|
27806
27818
|
}
|
|
27807
|
-
}
|
|
27808
|
-
|
|
27809
|
-
for (const pkg of nonCtxPackages) {
|
|
27810
|
-
try {
|
|
27811
|
-
await import(pkg.entrypoint);
|
|
27812
|
-
} catch {}
|
|
27819
|
+
} else {
|
|
27820
|
+
return { context: null, moduleAccess: new Map };
|
|
27813
27821
|
}
|
|
27814
27822
|
const { getContext, getAllModuleAccess } = await import(platformEntry);
|
|
27815
27823
|
return {
|
|
@@ -27826,19 +27834,21 @@ async function platformBuild(opts = {}) {
|
|
|
27826
27834
|
}
|
|
27827
27835
|
|
|
27828
27836
|
// src/commands/platform-deploy.ts
|
|
27829
|
-
import { existsSync as existsSync14, readFileSync as
|
|
27830
|
-
import { dirname as
|
|
27837
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
27838
|
+
import { dirname as dirname9, join as join17 } from "path";
|
|
27839
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
27831
27840
|
|
|
27832
27841
|
// src/deploy/bootstrap.ts
|
|
27833
|
-
|
|
27834
|
-
import {
|
|
27835
|
-
import {
|
|
27842
|
+
var {spawn: spawn4 } = globalThis.Bun;
|
|
27843
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
27844
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
27845
|
+
import { join as join15 } from "path";
|
|
27836
27846
|
|
|
27837
27847
|
// src/deploy/ansible.ts
|
|
27838
27848
|
import { spawn as nodeSpawn } from "child_process";
|
|
27839
|
-
import { mkdirSync as
|
|
27840
|
-
import { tmpdir
|
|
27841
|
-
import { join as
|
|
27849
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
27850
|
+
import { tmpdir } from "os";
|
|
27851
|
+
import { join as join12 } from "path";
|
|
27842
27852
|
|
|
27843
27853
|
// src/deploy/assets.ts
|
|
27844
27854
|
var TERRAFORM_MAIN_TF = `terraform {
|
|
@@ -28095,18 +28105,18 @@ var ASSETS = {
|
|
|
28095
28105
|
}
|
|
28096
28106
|
};
|
|
28097
28107
|
async function materializeAssets(targetDir, files) {
|
|
28098
|
-
const { mkdirSync:
|
|
28099
|
-
const { join:
|
|
28100
|
-
|
|
28108
|
+
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync10 } = await import("fs");
|
|
28109
|
+
const { join: join12 } = await import("path");
|
|
28110
|
+
mkdirSync10(targetDir, { recursive: true });
|
|
28101
28111
|
for (const [name, content] of Object.entries(files)) {
|
|
28102
|
-
|
|
28112
|
+
writeFileSync10(join12(targetDir, name), content);
|
|
28103
28113
|
}
|
|
28104
28114
|
}
|
|
28105
28115
|
|
|
28106
28116
|
// src/deploy/ansible.ts
|
|
28107
28117
|
async function runAnsible(inputs) {
|
|
28108
|
-
const workDir =
|
|
28109
|
-
|
|
28118
|
+
const workDir = join12(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
|
|
28119
|
+
mkdirSync10(workDir, { recursive: true });
|
|
28110
28120
|
await materializeAssets(workDir, ASSETS.ansible);
|
|
28111
28121
|
const user = inputs.asRoot ? "root" : inputs.target.user;
|
|
28112
28122
|
const port = inputs.ansible?.sshPort ?? inputs.target.port;
|
|
@@ -28120,7 +28130,7 @@ async function runAnsible(inputs) {
|
|
|
28120
28130
|
""
|
|
28121
28131
|
].join(`
|
|
28122
28132
|
`);
|
|
28123
|
-
|
|
28133
|
+
writeFileSync10(join12(workDir, "inventory.ini"), inventory);
|
|
28124
28134
|
const extraVarsJson = JSON.stringify({
|
|
28125
28135
|
username: inputs.target.user,
|
|
28126
28136
|
ssh_port: port,
|
|
@@ -28159,22 +28169,17 @@ function generateCaddyfile(cfg) {
|
|
|
28159
28169
|
lines.push("");
|
|
28160
28170
|
for (const [name, env2] of Object.entries(cfg.envs)) {
|
|
28161
28171
|
lines.push(`${env2.domain} {${tlsDirective}`);
|
|
28162
|
-
lines.push(" @deploy path /api/deploy /api/deploy/*");
|
|
28163
|
-
lines.push(" respond @deploy 404");
|
|
28164
|
-
lines.push("");
|
|
28165
28172
|
lines.push(` reverse_proxy arc-${name}:5005`);
|
|
28166
28173
|
lines.push("}");
|
|
28167
28174
|
lines.push("");
|
|
28168
28175
|
}
|
|
28169
|
-
lines.push(
|
|
28170
|
-
lines.push("
|
|
28171
|
-
lines.push("
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
}
|
|
28177
|
-
lines.push(" respond 404");
|
|
28176
|
+
lines.push(`${cfg.registry.domain} {${tlsDirective}`);
|
|
28177
|
+
lines.push(" reverse_proxy registry:5000 {");
|
|
28178
|
+
lines.push(" header_up Host {host}");
|
|
28179
|
+
lines.push(" }");
|
|
28180
|
+
lines.push(" request_body {");
|
|
28181
|
+
lines.push(" max_size 5GB");
|
|
28182
|
+
lines.push(" }");
|
|
28178
28183
|
lines.push("}");
|
|
28179
28184
|
return lines.join(`
|
|
28180
28185
|
`) + `
|
|
@@ -28182,8 +28187,7 @@ function generateCaddyfile(cfg) {
|
|
|
28182
28187
|
}
|
|
28183
28188
|
|
|
28184
28189
|
// src/deploy/compose.ts
|
|
28185
|
-
|
|
28186
|
-
function generateCompose({ cfg, cliVersion }) {
|
|
28190
|
+
function generateCompose({ cfg }) {
|
|
28187
28191
|
const lines = [];
|
|
28188
28192
|
lines.push("# Generated by `arc platform deploy` \u2014 do not edit by hand.");
|
|
28189
28193
|
lines.push("");
|
|
@@ -28194,7 +28198,6 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28194
28198
|
lines.push(" ports:");
|
|
28195
28199
|
lines.push(' - "80:80"');
|
|
28196
28200
|
lines.push(' - "443:443"');
|
|
28197
|
-
lines.push(' - "127.0.0.1:2019:2019"');
|
|
28198
28201
|
lines.push(" volumes:");
|
|
28199
28202
|
lines.push(" - ./Caddyfile:/etc/caddy/Caddyfile:ro");
|
|
28200
28203
|
lines.push(" - caddy_data:/data");
|
|
@@ -28202,25 +28205,39 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28202
28205
|
lines.push(" networks:");
|
|
28203
28206
|
lines.push(" - arc-net");
|
|
28204
28207
|
lines.push("");
|
|
28208
|
+
lines.push(" registry:");
|
|
28209
|
+
lines.push(" image: registry:2");
|
|
28210
|
+
lines.push(" restart: unless-stopped");
|
|
28211
|
+
lines.push(" volumes:");
|
|
28212
|
+
lines.push(" - registry_data:/var/lib/registry");
|
|
28213
|
+
lines.push(" - ./registry-auth/htpasswd:/auth/htpasswd:ro");
|
|
28214
|
+
lines.push(" environment:");
|
|
28215
|
+
lines.push(" REGISTRY_AUTH: htpasswd");
|
|
28216
|
+
lines.push(' REGISTRY_AUTH_HTPASSWD_REALM: "Arc Registry"');
|
|
28217
|
+
lines.push(" REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd");
|
|
28218
|
+
lines.push(' REGISTRY_HTTP_HOST: "https://' + cfg.registry.domain + '"');
|
|
28219
|
+
lines.push(" networks:");
|
|
28220
|
+
lines.push(" - arc-net");
|
|
28221
|
+
lines.push(" expose:");
|
|
28222
|
+
lines.push(' - "5000"');
|
|
28223
|
+
lines.push("");
|
|
28205
28224
|
for (const [name, env2] of Object.entries(cfg.envs)) {
|
|
28225
|
+
const upperName = name.toUpperCase().replace(/-/g, "_");
|
|
28206
28226
|
lines.push(` arc-${name}:`);
|
|
28207
|
-
lines.push(
|
|
28227
|
+
lines.push(` image: \${ARC_IMAGE_${upperName}:?Run \\\`arc platform deploy ${name}\\\` to publish an image first}`);
|
|
28228
|
+
lines.push(` container_name: arc-${name}`);
|
|
28208
28229
|
lines.push(" restart: unless-stopped");
|
|
28209
28230
|
lines.push(" volumes:");
|
|
28210
|
-
lines.push(` - arc-platform-${name}:/app/.arc/platform`);
|
|
28211
28231
|
lines.push(` - arc-data-${name}:/app/.arc/data`);
|
|
28212
|
-
lines.push(" - arc-cli-cache:/app/.arc/cli");
|
|
28213
|
-
lines.push(" - arc-bun-cache:/root/.bun/install/cache");
|
|
28214
28232
|
lines.push(" environment:");
|
|
28215
28233
|
lines.push(" PORT: 5005");
|
|
28216
|
-
lines.push(' ARC_DEPLOY_API: "1"');
|
|
28217
|
-
lines.push(` ARC_CLI_VERSION: ${JSON.stringify(cliVersion)}`);
|
|
28218
28234
|
const userEnv = env2.envVars ?? {};
|
|
28219
28235
|
if (!("NODE_ENV" in userEnv)) {
|
|
28220
28236
|
lines.push(" NODE_ENV: production");
|
|
28221
28237
|
}
|
|
28238
|
+
const reserved = new Set(["PORT"]);
|
|
28222
28239
|
for (const [k, v] of Object.entries(userEnv)) {
|
|
28223
|
-
if (
|
|
28240
|
+
if (reserved.has(k))
|
|
28224
28241
|
continue;
|
|
28225
28242
|
lines.push(` ${k}: ${JSON.stringify(v)}`);
|
|
28226
28243
|
}
|
|
@@ -28236,10 +28253,8 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28236
28253
|
lines.push("volumes:");
|
|
28237
28254
|
lines.push(" caddy_data:");
|
|
28238
28255
|
lines.push(" caddy_config:");
|
|
28239
|
-
lines.push("
|
|
28240
|
-
lines.push(" arc-bun-cache:");
|
|
28256
|
+
lines.push(" registry_data:");
|
|
28241
28257
|
for (const [name] of Object.entries(cfg.envs)) {
|
|
28242
|
-
lines.push(` arc-platform-${name}:`);
|
|
28243
28258
|
lines.push(` arc-data-${name}:`);
|
|
28244
28259
|
}
|
|
28245
28260
|
return lines.join(`
|
|
@@ -28247,16 +28262,29 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28247
28262
|
`;
|
|
28248
28263
|
}
|
|
28249
28264
|
|
|
28265
|
+
// src/deploy/htpasswd.ts
|
|
28266
|
+
async function generateHtpasswd(user, password) {
|
|
28267
|
+
if (!user || !password) {
|
|
28268
|
+
throw new Error("htpasswd: user and password must both be non-empty");
|
|
28269
|
+
}
|
|
28270
|
+
const hash = await Bun.password.hash(password, {
|
|
28271
|
+
algorithm: "bcrypt",
|
|
28272
|
+
cost: 10
|
|
28273
|
+
});
|
|
28274
|
+
return `${user}:${hash}
|
|
28275
|
+
`;
|
|
28276
|
+
}
|
|
28277
|
+
|
|
28250
28278
|
// src/deploy/terraform.ts
|
|
28251
28279
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
28252
28280
|
import { createHash as createHash2 } from "crypto";
|
|
28253
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
28281
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
28254
28282
|
import { homedir } from "os";
|
|
28255
|
-
import { join as
|
|
28283
|
+
import { join as join13 } from "path";
|
|
28256
28284
|
async function runTerraform(inputs) {
|
|
28257
28285
|
const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
|
|
28258
|
-
const workDir =
|
|
28259
|
-
|
|
28286
|
+
const workDir = join13(homedir(), ".arc-deploy", wsHash, "tf");
|
|
28287
|
+
mkdirSync11(workDir, { recursive: true });
|
|
28260
28288
|
await materializeAssets(workDir, ASSETS.terraform);
|
|
28261
28289
|
const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
|
|
28262
28290
|
if (!existsSync11(expandHome(sshPubKey))) {
|
|
@@ -28272,7 +28300,7 @@ async function runTerraform(inputs) {
|
|
|
28272
28300
|
].join(`
|
|
28273
28301
|
`) + `
|
|
28274
28302
|
`;
|
|
28275
|
-
|
|
28303
|
+
writeFileSync11(join13(workDir, "terraform.tfvars"), tfvars);
|
|
28276
28304
|
await runTf(workDir, ["init", "-input=false", "-no-color"]);
|
|
28277
28305
|
await runTf(workDir, [
|
|
28278
28306
|
"apply",
|
|
@@ -28330,11 +28358,11 @@ function expandHome(p) {
|
|
|
28330
28358
|
}
|
|
28331
28359
|
|
|
28332
28360
|
// src/deploy/config.ts
|
|
28333
|
-
import { existsSync as existsSync12, readFileSync as
|
|
28334
|
-
import { join as
|
|
28361
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
28362
|
+
import { join as join14 } from "path";
|
|
28335
28363
|
var DEPLOY_CONFIG_FILE = "deploy.arc.json";
|
|
28336
28364
|
function deployConfigPath(rootDir) {
|
|
28337
|
-
return
|
|
28365
|
+
return join14(rootDir, DEPLOY_CONFIG_FILE);
|
|
28338
28366
|
}
|
|
28339
28367
|
function deployConfigExists(rootDir) {
|
|
28340
28368
|
return existsSync12(deployConfigPath(rootDir));
|
|
@@ -28344,7 +28372,7 @@ function loadDeployConfig(rootDir) {
|
|
|
28344
28372
|
if (!existsSync12(path4)) {
|
|
28345
28373
|
throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
|
|
28346
28374
|
}
|
|
28347
|
-
const raw =
|
|
28375
|
+
const raw = readFileSync11(path4, "utf-8");
|
|
28348
28376
|
let parsed;
|
|
28349
28377
|
try {
|
|
28350
28378
|
parsed = JSON.parse(raw);
|
|
@@ -28356,9 +28384,9 @@ function loadDeployConfig(rootDir) {
|
|
|
28356
28384
|
}
|
|
28357
28385
|
function saveDeployConfig(rootDir, cfg) {
|
|
28358
28386
|
const path4 = deployConfigPath(rootDir);
|
|
28359
|
-
const raw = existsSync12(path4) ? JSON.parse(
|
|
28387
|
+
const raw = existsSync12(path4) ? JSON.parse(readFileSync11(path4, "utf-8")) : {};
|
|
28360
28388
|
raw.target = { ...raw.target, ...cfg.target };
|
|
28361
|
-
|
|
28389
|
+
writeFileSync12(path4, JSON.stringify(raw, null, 2) + `
|
|
28362
28390
|
`);
|
|
28363
28391
|
}
|
|
28364
28392
|
var VAR_REGEX = /\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g;
|
|
@@ -28384,6 +28412,15 @@ function validateDeployConfig(input) {
|
|
|
28384
28412
|
const target = requireObject(input, "target");
|
|
28385
28413
|
const envs = requireObject(input, "envs");
|
|
28386
28414
|
const caddy = requireObject(input, "caddy");
|
|
28415
|
+
const registry = requireObject(input, "registry");
|
|
28416
|
+
const registryDomain = requireString(registry, "registry.domain");
|
|
28417
|
+
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(registryDomain)) {
|
|
28418
|
+
throw new Error(`deploy.arc.json: registry.domain "${registryDomain}" doesn't look like a domain`);
|
|
28419
|
+
}
|
|
28420
|
+
const passwordEnv = requireString(registry, "registry.passwordEnv");
|
|
28421
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(passwordEnv)) {
|
|
28422
|
+
throw new Error(`deploy.arc.json: registry.passwordEnv "${passwordEnv}" must be an UPPER_SNAKE_CASE env var name`);
|
|
28423
|
+
}
|
|
28387
28424
|
const validated = {
|
|
28388
28425
|
target: {
|
|
28389
28426
|
host: requireString(target, "target.host"),
|
|
@@ -28395,6 +28432,11 @@ function validateDeployConfig(input) {
|
|
|
28395
28432
|
envs: {},
|
|
28396
28433
|
caddy: {
|
|
28397
28434
|
email: requireString(caddy, "caddy.email")
|
|
28435
|
+
},
|
|
28436
|
+
registry: {
|
|
28437
|
+
domain: registryDomain,
|
|
28438
|
+
username: optionalString(registry, "registry.username") ?? "deploy",
|
|
28439
|
+
passwordEnv
|
|
28398
28440
|
}
|
|
28399
28441
|
};
|
|
28400
28442
|
const envKeys = Object.keys(envs);
|
|
@@ -28516,19 +28558,17 @@ async function streamToString(stream2) {
|
|
|
28516
28558
|
return new Response(stream2).text();
|
|
28517
28559
|
}
|
|
28518
28560
|
function baseSshArgs(target) {
|
|
28519
|
-
const
|
|
28520
|
-
return [
|
|
28561
|
+
const args = [
|
|
28521
28562
|
"-o",
|
|
28522
28563
|
"BatchMode=yes",
|
|
28523
28564
|
"-o",
|
|
28524
28565
|
"StrictHostKeyChecking=accept-new",
|
|
28525
|
-
"-o",
|
|
28526
|
-
"IdentitiesOnly=yes",
|
|
28527
|
-
"-i",
|
|
28528
|
-
key,
|
|
28529
28566
|
"-p",
|
|
28530
28567
|
String(target.port)
|
|
28531
28568
|
];
|
|
28569
|
+
if (target.sshKey)
|
|
28570
|
+
args.push("-i", target.sshKey);
|
|
28571
|
+
return args;
|
|
28532
28572
|
}
|
|
28533
28573
|
async function sshExec(target, cmd, opts = {}) {
|
|
28534
28574
|
const args = [
|
|
@@ -28582,19 +28622,16 @@ async function waitForSsh(target, opts = {}) {
|
|
|
28582
28622
|
throw new Error(`Timed out waiting for SSH on ${target.user}@${target.host}`);
|
|
28583
28623
|
}
|
|
28584
28624
|
async function scpUpload(target, localPath, remotePath) {
|
|
28585
|
-
const key = target.sshKey ?? `${process.env.HOME}/.ssh/id_ed25519`;
|
|
28586
28625
|
const args = [
|
|
28587
28626
|
"-o",
|
|
28588
28627
|
"BatchMode=yes",
|
|
28589
28628
|
"-o",
|
|
28590
28629
|
"StrictHostKeyChecking=accept-new",
|
|
28591
|
-
"-o",
|
|
28592
|
-
"IdentitiesOnly=yes",
|
|
28593
|
-
"-i",
|
|
28594
|
-
key,
|
|
28595
28630
|
"-P",
|
|
28596
28631
|
String(target.port)
|
|
28597
28632
|
];
|
|
28633
|
+
if (target.sshKey)
|
|
28634
|
+
args.push("-i", target.sshKey);
|
|
28598
28635
|
args.push(localPath, `${target.user}@${target.host}:${remotePath}`);
|
|
28599
28636
|
const proc2 = spawn3({ cmd: ["scp", ...args], stderr: "pipe" });
|
|
28600
28637
|
const [stderr, exitCode] = await Promise.all([
|
|
@@ -28605,52 +28642,6 @@ async function scpUpload(target, localPath, remotePath) {
|
|
|
28605
28642
|
throw new Error(`scp failed (${exitCode}): ${stderr}`);
|
|
28606
28643
|
}
|
|
28607
28644
|
}
|
|
28608
|
-
async function openTunnel(target, localPort, remoteHost, remotePort) {
|
|
28609
|
-
const args = [
|
|
28610
|
-
...baseSshArgs(target),
|
|
28611
|
-
"-N",
|
|
28612
|
-
"-L",
|
|
28613
|
-
`${localPort}:${remoteHost}:${remotePort}`,
|
|
28614
|
-
`${target.user}@${target.host}`
|
|
28615
|
-
];
|
|
28616
|
-
const proc2 = spawn3({
|
|
28617
|
-
cmd: ["ssh", ...args],
|
|
28618
|
-
stdin: "ignore",
|
|
28619
|
-
stdout: "pipe",
|
|
28620
|
-
stderr: "pipe"
|
|
28621
|
-
});
|
|
28622
|
-
const deadline = Date.now() + 1e4;
|
|
28623
|
-
let lastErr;
|
|
28624
|
-
while (Date.now() < deadline) {
|
|
28625
|
-
if (proc2.exitCode !== null) {
|
|
28626
|
-
const stderr = await streamToString(proc2.stderr);
|
|
28627
|
-
throw new Error(`ssh tunnel exited early: ${stderr}`);
|
|
28628
|
-
}
|
|
28629
|
-
try {
|
|
28630
|
-
const probe = await Bun.connect({
|
|
28631
|
-
hostname: "127.0.0.1",
|
|
28632
|
-
port: localPort,
|
|
28633
|
-
socket: { data() {}, open() {}, close() {}, error() {} }
|
|
28634
|
-
});
|
|
28635
|
-
probe.end();
|
|
28636
|
-
return {
|
|
28637
|
-
localPort,
|
|
28638
|
-
close() {
|
|
28639
|
-
try {
|
|
28640
|
-
proc2.kill();
|
|
28641
|
-
} catch {}
|
|
28642
|
-
}
|
|
28643
|
-
};
|
|
28644
|
-
} catch (e) {
|
|
28645
|
-
lastErr = e;
|
|
28646
|
-
await Bun.sleep(200);
|
|
28647
|
-
}
|
|
28648
|
-
}
|
|
28649
|
-
try {
|
|
28650
|
-
proc2.kill();
|
|
28651
|
-
} catch {}
|
|
28652
|
-
throw new Error(`Failed to establish SSH tunnel on localhost:${localPort}: ${String(lastErr)}`);
|
|
28653
|
-
}
|
|
28654
28645
|
|
|
28655
28646
|
// src/deploy/remote-state.ts
|
|
28656
28647
|
var STATE_MARKER_PATH = "/opt/arc/.arc-state.json";
|
|
@@ -28733,202 +28724,373 @@ async function bootstrap(inputs) {
|
|
|
28733
28724
|
});
|
|
28734
28725
|
ok("Host bootstrapped");
|
|
28735
28726
|
}
|
|
28736
|
-
if (state.kind !== "ready") {
|
|
28737
|
-
await upStack(inputs);
|
|
28738
|
-
ok("Docker stack up");
|
|
28727
|
+
if (state.kind !== "ready") {
|
|
28728
|
+
await upStack(inputs);
|
|
28729
|
+
ok("Docker stack up");
|
|
28730
|
+
}
|
|
28731
|
+
await writeStateMarker(cfg.target, {
|
|
28732
|
+
cliVersion: inputs.cliVersion,
|
|
28733
|
+
configHash: inputs.configHash,
|
|
28734
|
+
updatedAt: new Date().toISOString()
|
|
28735
|
+
});
|
|
28736
|
+
}
|
|
28737
|
+
async function upStack(inputs) {
|
|
28738
|
+
const { cfg } = inputs;
|
|
28739
|
+
const workDir = join15(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
|
|
28740
|
+
mkdirSync12(workDir, { recursive: true });
|
|
28741
|
+
await assertRegistryDnsResolves(cfg);
|
|
28742
|
+
const password = process.env[cfg.registry.passwordEnv];
|
|
28743
|
+
if (!password) {
|
|
28744
|
+
throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set. ` + `Set it (e.g. \`export ${cfg.registry.passwordEnv}=...\`) before bootstrap.`);
|
|
28745
|
+
}
|
|
28746
|
+
const htpasswdLine = await generateHtpasswd(cfg.registry.username, password);
|
|
28747
|
+
writeFileSync13(join15(workDir, "htpasswd"), htpasswdLine);
|
|
28748
|
+
writeFileSync13(join15(workDir, "Caddyfile"), generateCaddyfile(cfg));
|
|
28749
|
+
writeFileSync13(join15(workDir, "docker-compose.yml"), generateCompose({ cfg }));
|
|
28750
|
+
await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
|
|
28751
|
+
for (const name of Object.keys(cfg.envs)) {
|
|
28752
|
+
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
|
|
28753
|
+
}
|
|
28754
|
+
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/registry-auth`);
|
|
28755
|
+
await scpUpload(cfg.target, join15(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
|
|
28756
|
+
await scpUpload(cfg.target, join15(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
|
|
28757
|
+
await scpUpload(cfg.target, join15(workDir, "htpasswd"), `${cfg.target.remoteDir}/registry-auth/htpasswd`);
|
|
28758
|
+
await assertExec(cfg.target, `touch ${cfg.target.remoteDir}/.env`);
|
|
28759
|
+
await assertExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose pull --ignore-pull-failures caddy registry && docker compose up -d caddy registry`);
|
|
28760
|
+
await sshDockerLogin(cfg);
|
|
28761
|
+
const knownEnvs = await listConfiguredEnvs(cfg);
|
|
28762
|
+
if (knownEnvs.length > 0) {
|
|
28763
|
+
await assertExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose up -d ${knownEnvs.map((e) => `arc-${e}`).join(" ")}`);
|
|
28764
|
+
}
|
|
28765
|
+
}
|
|
28766
|
+
async function sshDockerLogin(cfg) {
|
|
28767
|
+
const password = process.env[cfg.registry.passwordEnv];
|
|
28768
|
+
if (!password) {
|
|
28769
|
+
throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set on the deploy host (CLI machine).`);
|
|
28770
|
+
}
|
|
28771
|
+
const cmd = `echo "$ARC_REGISTRY_PASSWORD_FORWARDED" | docker login ${cfg.registry.domain} -u ${cfg.registry.username} --password-stdin`;
|
|
28772
|
+
const proc2 = spawn4({
|
|
28773
|
+
cmd: [
|
|
28774
|
+
"ssh",
|
|
28775
|
+
...baseSshArgs(cfg.target),
|
|
28776
|
+
`${cfg.target.user}@${cfg.target.host}`,
|
|
28777
|
+
"--",
|
|
28778
|
+
`ARC_REGISTRY_PASSWORD_FORWARDED='${password.replace(/'/g, "'\\''")}' bash -c ${JSON.stringify(cmd)}`
|
|
28779
|
+
],
|
|
28780
|
+
stdout: "pipe",
|
|
28781
|
+
stderr: "pipe"
|
|
28782
|
+
});
|
|
28783
|
+
const exit = await proc2.exited;
|
|
28784
|
+
if (exit !== 0) {
|
|
28785
|
+
const stderr = await new Response(proc2.stderr).text();
|
|
28786
|
+
throw new Error(`Server-side docker login failed (exit ${exit}): ${stderr.trim()}`);
|
|
28787
|
+
}
|
|
28788
|
+
}
|
|
28789
|
+
async function listConfiguredEnvs(cfg) {
|
|
28790
|
+
const res = await sshExec(cfg.target, `cat ${cfg.target.remoteDir}/.env 2>/dev/null || true`, { quiet: true });
|
|
28791
|
+
const set = new Set;
|
|
28792
|
+
for (const line of res.stdout.split(`
|
|
28793
|
+
`)) {
|
|
28794
|
+
const m = line.match(/^ARC_IMAGE_([A-Z0-9_]+)=/);
|
|
28795
|
+
if (!m)
|
|
28796
|
+
continue;
|
|
28797
|
+
const lowerName = m[1].toLowerCase().replace(/_/g, "-");
|
|
28798
|
+
if (lowerName in cfg.envs)
|
|
28799
|
+
set.add(lowerName);
|
|
28739
28800
|
}
|
|
28740
|
-
|
|
28741
|
-
|
|
28742
|
-
|
|
28743
|
-
|
|
28801
|
+
return [...set];
|
|
28802
|
+
}
|
|
28803
|
+
async function assertRegistryDnsResolves(cfg) {
|
|
28804
|
+
const proc2 = spawn4({
|
|
28805
|
+
cmd: ["dig", "+short", "+time=3", "+tries=1", cfg.registry.domain],
|
|
28806
|
+
stdout: "pipe",
|
|
28807
|
+
stderr: "ignore"
|
|
28744
28808
|
});
|
|
28809
|
+
const exit = await proc2.exited;
|
|
28810
|
+
if (exit !== 0) {
|
|
28811
|
+
err(`\`dig\` is not available \u2014 skipping DNS pre-flight for ${cfg.registry.domain}.`);
|
|
28812
|
+
return;
|
|
28813
|
+
}
|
|
28814
|
+
const resolved = (await new Response(proc2.stdout).text()).split(`
|
|
28815
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
28816
|
+
if (resolved.length === 0) {
|
|
28817
|
+
throw new Error(`Registry DNS not configured: ${cfg.registry.domain} doesn't resolve. ` + `Add an A record pointing to ${cfg.target.host} and re-run deploy.`);
|
|
28818
|
+
}
|
|
28819
|
+
if (!resolved.includes(cfg.target.host)) {
|
|
28820
|
+
throw new Error(`Registry DNS mismatch: ${cfg.registry.domain} resolves to [${resolved.join(", ")}], ` + `but target host is ${cfg.target.host}. Update the A record before continuing.`);
|
|
28821
|
+
}
|
|
28822
|
+
}
|
|
28823
|
+
|
|
28824
|
+
// src/deploy/deploy-env.ts
|
|
28825
|
+
var HEALTH_RETRIES = 10;
|
|
28826
|
+
var HEALTH_DELAY_MS = 1000;
|
|
28827
|
+
async function updateEnvDeployment(opts) {
|
|
28828
|
+
const { target, cfg, env: env2, fullRef } = opts;
|
|
28829
|
+
const upperEnv = env2.toUpperCase().replace(/-/g, "_");
|
|
28830
|
+
const envVarName = `ARC_IMAGE_${upperEnv}`;
|
|
28831
|
+
const envPath = `${cfg.target.remoteDir}/.env`;
|
|
28832
|
+
const escapedRef = fullRef.replace(/"/g, "\\\"");
|
|
28833
|
+
const updateScript = [
|
|
28834
|
+
`touch ${envPath}`,
|
|
28835
|
+
`awk -v line="${envVarName}=${escapedRef}" -v key="${envVarName}=" '`,
|
|
28836
|
+
` BEGIN { replaced=0 } `,
|
|
28837
|
+
` $0 ~ "^"key { print line; replaced=1; next } `,
|
|
28838
|
+
` { print } `,
|
|
28839
|
+
` END { if (!replaced) print line } `,
|
|
28840
|
+
`' ${envPath} > ${envPath}.tmp && mv ${envPath}.tmp ${envPath}`
|
|
28841
|
+
].join("");
|
|
28842
|
+
await assertExec(target, updateScript);
|
|
28843
|
+
await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose pull arc-${env2}`);
|
|
28844
|
+
await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose up -d arc-${env2}`);
|
|
28845
|
+
const retain = opts.retainImages ?? 3;
|
|
28846
|
+
const imageBaseName = imageBaseFromRef(fullRef);
|
|
28847
|
+
if (imageBaseName) {
|
|
28848
|
+
const pruneScript = [
|
|
28849
|
+
`docker images "${imageBaseName}" --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" `,
|
|
28850
|
+
`| grep -v ":latest " `,
|
|
28851
|
+
`| sort -k2,3 -r `,
|
|
28852
|
+
`| tail -n +${retain + 1} `,
|
|
28853
|
+
`| awk '{print $1}' `,
|
|
28854
|
+
`| xargs -r docker rmi 2>/dev/null || true`
|
|
28855
|
+
].join("");
|
|
28856
|
+
await sshExec(target, pruneScript, { quiet: true });
|
|
28857
|
+
}
|
|
28858
|
+
const ok2 = await healthCheck(target, env2);
|
|
28859
|
+
return { env: env2, fullRef, redeployed: ok2 };
|
|
28860
|
+
}
|
|
28861
|
+
function imageBaseFromRef(fullRef) {
|
|
28862
|
+
const colonIdx = fullRef.lastIndexOf(":");
|
|
28863
|
+
return colonIdx > 0 ? fullRef.slice(0, colonIdx) : null;
|
|
28864
|
+
}
|
|
28865
|
+
async function healthCheck(target, env2) {
|
|
28866
|
+
for (let i = 0;i < HEALTH_RETRIES; i++) {
|
|
28867
|
+
const res = await sshExec(target, `docker exec arc-${env2} wget -qO- http://localhost:5005/health 2>&1 || echo HEALTHCHECK_FAILED`, { quiet: true });
|
|
28868
|
+
if (res.exitCode === 0 && !res.stdout.includes("HEALTHCHECK_FAILED")) {
|
|
28869
|
+
return true;
|
|
28870
|
+
}
|
|
28871
|
+
await sleep(HEALTH_DELAY_MS);
|
|
28872
|
+
}
|
|
28873
|
+
return false;
|
|
28745
28874
|
}
|
|
28746
|
-
|
|
28747
|
-
|
|
28748
|
-
|
|
28749
|
-
|
|
28750
|
-
|
|
28751
|
-
|
|
28752
|
-
|
|
28753
|
-
|
|
28754
|
-
|
|
28875
|
+
function sleep(ms) {
|
|
28876
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
28877
|
+
}
|
|
28878
|
+
|
|
28879
|
+
// src/deploy/image.ts
|
|
28880
|
+
var {spawn: spawn5 } = globalThis.Bun;
|
|
28881
|
+
import { createHash as createHash3 } from "crypto";
|
|
28882
|
+
import {
|
|
28883
|
+
copyFileSync as copyFileSync2,
|
|
28884
|
+
existsSync as existsSync13,
|
|
28885
|
+
mkdirSync as mkdirSync13,
|
|
28886
|
+
readFileSync as readFileSync12,
|
|
28887
|
+
realpathSync as realpathSync3,
|
|
28888
|
+
writeFileSync as writeFileSync14
|
|
28889
|
+
} from "fs";
|
|
28890
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
28891
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
28892
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
28893
|
+
|
|
28894
|
+
// src/deploy/image-template.ts
|
|
28895
|
+
function generateDockerfile(inputs) {
|
|
28896
|
+
const conditionalCopies = [];
|
|
28897
|
+
if (inputs.hasPublicDir) {
|
|
28898
|
+
conditionalCopies.push("COPY public/ /app/public/");
|
|
28899
|
+
}
|
|
28900
|
+
if (inputs.hasManifest && inputs.manifestPath) {
|
|
28901
|
+
conditionalCopies.push(`COPY ${inputs.manifestPath} /app/${inputs.manifestPath}`);
|
|
28902
|
+
}
|
|
28903
|
+
if (inputs.hasLocales) {
|
|
28904
|
+
conditionalCopies.push("COPY locales/ /app/locales/");
|
|
28755
28905
|
}
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28906
|
+
return [
|
|
28907
|
+
"# Generated by `arc platform deploy` \u2014 do not edit by hand.",
|
|
28908
|
+
"FROM oven/bun:1-alpine",
|
|
28909
|
+
"",
|
|
28910
|
+
"RUN apk add --no-cache tini ca-certificates",
|
|
28911
|
+
"",
|
|
28912
|
+
"WORKDIR /app",
|
|
28913
|
+
"",
|
|
28914
|
+
"# Layer 1 \u2014 framework peers as /app/package.json + install into /app/node_modules.",
|
|
28915
|
+
"# resolveWorkspace() walks up looking for the first package.json (finds /app/),",
|
|
28916
|
+
"# loadServerContext resolves @arcote.tech/platform from /app/node_modules.",
|
|
28917
|
+
"# Cached, invalidates only on .arc/platform/package.json change.",
|
|
28918
|
+
"COPY .arc/platform/package.json /app/package.json",
|
|
28919
|
+
"RUN cd /app && bun install --production --no-save",
|
|
28920
|
+
"",
|
|
28921
|
+
"# Layer 2 \u2014 user artifacts (chunks, shell, styles, server bundles, host.js).",
|
|
28922
|
+
"# host.js IS the arc-cli bundle \u2014 `arc platform deploy` copied it here from",
|
|
28923
|
+
"# whatever arc-cli was on the user's PATH. No npm dependency at runtime.",
|
|
28924
|
+
"COPY .arc/platform/ /app/.arc/platform/",
|
|
28925
|
+
...conditionalCopies,
|
|
28926
|
+
"",
|
|
28927
|
+
"ENV PORT=5005",
|
|
28928
|
+
"ENV NODE_ENV=production",
|
|
28929
|
+
"EXPOSE 5005",
|
|
28930
|
+
"",
|
|
28931
|
+
'ENTRYPOINT ["tini", "--"]',
|
|
28932
|
+
'CMD ["bun", "run", "/app/.arc/platform/host.js", "platform", "start"]',
|
|
28933
|
+
""
|
|
28934
|
+
].join(`
|
|
28935
|
+
`);
|
|
28759
28936
|
}
|
|
28760
28937
|
|
|
28761
|
-
// src/deploy/
|
|
28762
|
-
|
|
28763
|
-
|
|
28764
|
-
|
|
28765
|
-
|
|
28766
|
-
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
|
|
28770
|
-
}
|
|
28771
|
-
}
|
|
28772
|
-
|
|
28773
|
-
const
|
|
28774
|
-
const
|
|
28775
|
-
|
|
28776
|
-
|
|
28777
|
-
const
|
|
28778
|
-
|
|
28779
|
-
|
|
28780
|
-
|
|
28781
|
-
|
|
28782
|
-
|
|
28783
|
-
|
|
28784
|
-
|
|
28785
|
-
|
|
28786
|
-
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
|
|
28795
|
-
|
|
28796
|
-
|
|
28797
|
-
|
|
28798
|
-
|
|
28799
|
-
|
|
28800
|
-
|
|
28801
|
-
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
28806
|
-
|
|
28807
|
-
|
|
28808
|
-
|
|
28809
|
-
|
|
28810
|
-
|
|
28811
|
-
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
|
|
28819
|
-
const safeName = sanitizeName(mod.name);
|
|
28820
|
-
const moduleDir = join17(ws.modulesDir, safeName);
|
|
28821
|
-
const browserPath = join17(moduleDir, "browser.js");
|
|
28822
|
-
const serverPath = join17(moduleDir, "server.js");
|
|
28823
|
-
const pkgPath = join17(moduleDir, "package.json");
|
|
28824
|
-
const accessPath = join17(moduleDir, "access.json");
|
|
28825
|
-
const browserActual = existsSync13(browserPath) ? browserPath : join17(ws.modulesDir, `${safeName}.js`);
|
|
28826
|
-
if (!existsSync13(browserActual)) {
|
|
28827
|
-
throw new Error(`Missing browser bundle for module ${mod.name}`);
|
|
28828
|
-
}
|
|
28829
|
-
const form = new FormData;
|
|
28830
|
-
form.append("browser.js", new Blob([readFileSync13(browserActual)]), "browser.js");
|
|
28831
|
-
const pkg = pkgByName.get(safeName);
|
|
28832
|
-
if (pkg && isContextPackage(pkg.packageJson) && existsSync13(serverPath)) {
|
|
28833
|
-
form.append("server.js", new Blob([readFileSync13(serverPath)]), "server.js");
|
|
28834
|
-
}
|
|
28835
|
-
if (existsSync13(pkgPath)) {
|
|
28836
|
-
form.append("package.json", new Blob([readFileSync13(pkgPath)]), "package.json");
|
|
28837
|
-
}
|
|
28838
|
-
if (existsSync13(accessPath)) {
|
|
28839
|
-
form.append("access.json", new Blob([readFileSync13(accessPath)]), "access.json");
|
|
28840
|
-
}
|
|
28841
|
-
console.log(`[arc] Pushing module ${safeName}...`);
|
|
28842
|
-
const res = await fetch(`${base2()}/api/deploy/modules/${safeName}`, {
|
|
28843
|
-
method: "POST",
|
|
28844
|
-
body: form
|
|
28845
|
-
});
|
|
28846
|
-
if (!res.ok) {
|
|
28847
|
-
throw new Error(`module ${safeName} push failed: ${res.status} ${await res.text()}`);
|
|
28848
|
-
}
|
|
28849
|
-
}
|
|
28850
|
-
if (diff.stylesChanged) {
|
|
28851
|
-
console.log("[arc] Pushing styles...");
|
|
28852
|
-
const form = new FormData;
|
|
28853
|
-
for (const name of ["styles.css", "theme.css"]) {
|
|
28854
|
-
const p = join17(ws.arcDir, name);
|
|
28855
|
-
if (existsSync13(p)) {
|
|
28856
|
-
form.append(name, new Blob([readFileSync13(p)]), name);
|
|
28938
|
+
// src/deploy/image.ts
|
|
28939
|
+
async function buildImage(ws, opts) {
|
|
28940
|
+
await ensureDocker();
|
|
28941
|
+
const manifestPath = join16(ws.modulesDir, "manifest.json");
|
|
28942
|
+
if (!existsSync13(manifestPath)) {
|
|
28943
|
+
throw new Error(`No build manifest at ${manifestPath}. Run \`arc platform build\` first or omit --skip-build.`);
|
|
28944
|
+
}
|
|
28945
|
+
embedCliBundle(ws);
|
|
28946
|
+
const contentHash = computeContentHash(manifestPath);
|
|
28947
|
+
const imageTag = `${opts.imageName}:${contentHash}`;
|
|
28948
|
+
const fullRef = opts.registryDomain ? `${opts.registryDomain}/${imageTag}` : imageTag;
|
|
28949
|
+
const dockerfileInputs = collectDockerfileInputs(ws);
|
|
28950
|
+
const dockerfile = generateDockerfile(dockerfileInputs);
|
|
28951
|
+
const buildContextDir = ws.rootDir;
|
|
28952
|
+
const dockerfileDir = join16(tmpdir3(), `arc-image-${Date.now()}`);
|
|
28953
|
+
mkdirSync13(dockerfileDir, { recursive: true });
|
|
28954
|
+
const dockerfilePath = join16(dockerfileDir, "Dockerfile");
|
|
28955
|
+
writeFileSync14(dockerfilePath, dockerfile);
|
|
28956
|
+
const buildArgs = [
|
|
28957
|
+
"build",
|
|
28958
|
+
"-f",
|
|
28959
|
+
dockerfilePath,
|
|
28960
|
+
"-t",
|
|
28961
|
+
fullRef,
|
|
28962
|
+
"-t",
|
|
28963
|
+
`${opts.imageName}:latest`,
|
|
28964
|
+
buildContextDir
|
|
28965
|
+
];
|
|
28966
|
+
const proc2 = spawn5({
|
|
28967
|
+
cmd: ["docker", ...buildArgs],
|
|
28968
|
+
stdout: "inherit",
|
|
28969
|
+
stderr: "inherit"
|
|
28970
|
+
});
|
|
28971
|
+
const exit = await proc2.exited;
|
|
28972
|
+
if (exit !== 0) {
|
|
28973
|
+
throw new Error(`docker build failed (exit ${exit})`);
|
|
28974
|
+
}
|
|
28975
|
+
return { imageTag, fullRef, contentHash };
|
|
28976
|
+
}
|
|
28977
|
+
function embedCliBundle(ws) {
|
|
28978
|
+
const source = locateCliBundle();
|
|
28979
|
+
const target = join16(ws.arcDir, "host.js");
|
|
28980
|
+
copyFileSync2(source, target);
|
|
28981
|
+
}
|
|
28982
|
+
function locateCliBundle() {
|
|
28983
|
+
const here = fileURLToPath6(import.meta.url);
|
|
28984
|
+
let cur = dirname8(here);
|
|
28985
|
+
while (cur !== "/" && cur !== "") {
|
|
28986
|
+
const candidate = join16(cur, "package.json");
|
|
28987
|
+
if (existsSync13(candidate)) {
|
|
28988
|
+
try {
|
|
28989
|
+
const pkg = JSON.parse(readFileSync12(candidate, "utf-8"));
|
|
28990
|
+
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
28991
|
+
const distIndex = join16(realpathSync3(cur), "dist", "index.js");
|
|
28992
|
+
if (!existsSync13(distIndex)) {
|
|
28993
|
+
throw new Error(`arc-cli bundle missing at ${distIndex}. Run \`bun run build\` in packages/cli/.`);
|
|
28994
|
+
}
|
|
28995
|
+
return distIndex;
|
|
28857
28996
|
}
|
|
28997
|
+
} catch (e) {
|
|
28998
|
+
if (e instanceof Error && e.message.startsWith("arc-cli bundle"))
|
|
28999
|
+
throw e;
|
|
28858
29000
|
}
|
|
28859
|
-
const res = await fetch(`${base2()}/api/deploy/styles`, {
|
|
28860
|
-
method: "POST",
|
|
28861
|
-
body: form
|
|
28862
|
-
});
|
|
28863
|
-
if (!res.ok) {
|
|
28864
|
-
throw new Error(`styles push failed: ${res.status} ${await res.text()}`);
|
|
28865
|
-
}
|
|
28866
|
-
}
|
|
28867
|
-
const commitRes = await fetch(`${base2()}/api/deploy/manifest`, {
|
|
28868
|
-
method: "POST",
|
|
28869
|
-
headers: { "Content-Type": "application/json" },
|
|
28870
|
-
body: JSON.stringify(localManifest)
|
|
28871
|
-
});
|
|
28872
|
-
if (!commitRes.ok) {
|
|
28873
|
-
throw new Error(`manifest commit failed: ${commitRes.status} ${await commitRes.text()}`);
|
|
28874
29001
|
}
|
|
28875
|
-
const
|
|
28876
|
-
if (
|
|
28877
|
-
|
|
28878
|
-
|
|
28879
|
-
}
|
|
28880
|
-
return {
|
|
28881
|
-
env: env2,
|
|
28882
|
-
frameworkChanged,
|
|
28883
|
-
changedModules: diff.changedModules.map((m) => m.name),
|
|
28884
|
-
stylesChanged: diff.stylesChanged,
|
|
28885
|
-
restarts
|
|
28886
|
-
};
|
|
28887
|
-
} finally {
|
|
28888
|
-
tunnel.close();
|
|
29002
|
+
const parent = dirname8(cur);
|
|
29003
|
+
if (parent === cur)
|
|
29004
|
+
break;
|
|
29005
|
+
cur = parent;
|
|
28889
29006
|
}
|
|
29007
|
+
throw new Error("Could not locate @arcote.tech/arc-cli package.json walking up from " + here);
|
|
28890
29008
|
}
|
|
28891
|
-
function
|
|
28892
|
-
|
|
28893
|
-
|
|
28894
|
-
|
|
28895
|
-
|
|
28896
|
-
|
|
28897
|
-
|
|
28898
|
-
|
|
28899
|
-
|
|
28900
|
-
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
28906
|
-
|
|
28907
|
-
|
|
28908
|
-
const
|
|
28909
|
-
|
|
28910
|
-
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
|
|
28914
|
-
|
|
28915
|
-
|
|
28916
|
-
|
|
28917
|
-
|
|
28918
|
-
|
|
28919
|
-
lastErr = e;
|
|
29009
|
+
async function ensureDocker() {
|
|
29010
|
+
try {
|
|
29011
|
+
const proc2 = spawn5({
|
|
29012
|
+
cmd: ["docker", "--version"],
|
|
29013
|
+
stdout: "pipe",
|
|
29014
|
+
stderr: "pipe"
|
|
29015
|
+
});
|
|
29016
|
+
const exit = await proc2.exited;
|
|
29017
|
+
if (exit !== 0)
|
|
29018
|
+
throw new Error("docker --version exited non-zero");
|
|
29019
|
+
} catch {
|
|
29020
|
+
throw new Error("Docker is not available on PATH. Install Docker Desktop (or docker engine + buildx) before running deploy.");
|
|
29021
|
+
}
|
|
29022
|
+
}
|
|
29023
|
+
function computeContentHash(manifestPath) {
|
|
29024
|
+
const raw = JSON.parse(readFileSync12(manifestPath, "utf-8"));
|
|
29025
|
+
delete raw.buildTime;
|
|
29026
|
+
const canonical = JSON.stringify(raw);
|
|
29027
|
+
return createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
29028
|
+
}
|
|
29029
|
+
function collectDockerfileInputs(ws) {
|
|
29030
|
+
const hasPublicDir = existsSync13(join16(ws.rootDir, "public"));
|
|
29031
|
+
const hasLocales = existsSync13(join16(ws.rootDir, "locales"));
|
|
29032
|
+
let manifestPath;
|
|
29033
|
+
for (const name of ["manifest.webmanifest", "manifest.json"]) {
|
|
29034
|
+
if (existsSync13(join16(ws.rootDir, name))) {
|
|
29035
|
+
manifestPath = name;
|
|
29036
|
+
break;
|
|
28920
29037
|
}
|
|
28921
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
28922
29038
|
}
|
|
28923
|
-
|
|
29039
|
+
return {
|
|
29040
|
+
hasPublicDir,
|
|
29041
|
+
hasManifest: !!manifestPath,
|
|
29042
|
+
manifestPath,
|
|
29043
|
+
hasLocales
|
|
29044
|
+
};
|
|
29045
|
+
}
|
|
29046
|
+
function sanitizeImageName(name) {
|
|
29047
|
+
const cleaned = name.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
29048
|
+
return cleaned || "arc-app";
|
|
29049
|
+
}
|
|
29050
|
+
|
|
29051
|
+
// src/deploy/registry.ts
|
|
29052
|
+
var {spawn: spawn6 } = globalThis.Bun;
|
|
29053
|
+
async function dockerLogin(registry) {
|
|
29054
|
+
const password = process.env[registry.passwordEnv];
|
|
29055
|
+
if (!password) {
|
|
29056
|
+
throw new Error(`Registry password env var ${registry.passwordEnv} is not set. ` + `Set it in your shell before running deploy.`);
|
|
29057
|
+
}
|
|
29058
|
+
const proc2 = spawn6({
|
|
29059
|
+
cmd: [
|
|
29060
|
+
"docker",
|
|
29061
|
+
"login",
|
|
29062
|
+
registry.domain,
|
|
29063
|
+
"-u",
|
|
29064
|
+
registry.username,
|
|
29065
|
+
"--password-stdin"
|
|
29066
|
+
],
|
|
29067
|
+
stdin: "pipe",
|
|
29068
|
+
stdout: "pipe",
|
|
29069
|
+
stderr: "pipe"
|
|
29070
|
+
});
|
|
29071
|
+
proc2.stdin.write(password);
|
|
29072
|
+
await proc2.stdin.end();
|
|
29073
|
+
const exit = await proc2.exited;
|
|
29074
|
+
if (exit !== 0) {
|
|
29075
|
+
const stderr = await new Response(proc2.stderr).text();
|
|
29076
|
+
throw new Error(`docker login ${registry.domain} failed (exit ${exit}): ${stderr.trim()}`);
|
|
29077
|
+
}
|
|
28924
29078
|
}
|
|
28925
|
-
function
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
29079
|
+
async function dockerPush(fullRef) {
|
|
29080
|
+
const proc2 = spawn6({
|
|
29081
|
+
cmd: ["docker", "push", fullRef],
|
|
29082
|
+
stdout: "inherit",
|
|
29083
|
+
stderr: "inherit"
|
|
29084
|
+
});
|
|
29085
|
+
const exit = await proc2.exited;
|
|
29086
|
+
if (exit !== 0) {
|
|
29087
|
+
throw new Error(`docker push ${fullRef} failed (exit ${exit})`);
|
|
29088
|
+
}
|
|
28930
29089
|
}
|
|
28931
29090
|
|
|
29091
|
+
// ../../node_modules/.bun/@clack+prompts@0.9.1/node_modules/@clack/prompts/dist/index.mjs
|
|
29092
|
+
import { stripVTControlCharacters as T2 } from "util";
|
|
29093
|
+
|
|
28932
29094
|
// ../../node_modules/.bun/@clack+core@0.4.1/node_modules/@clack/core/dist/index.mjs
|
|
28933
29095
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
28934
29096
|
import { stdin as $, stdout as j } from "process";
|
|
@@ -29452,6 +29614,21 @@ ${import_picocolors2.default.cyan(m2)}
|
|
|
29452
29614
|
}
|
|
29453
29615
|
} }).prompt();
|
|
29454
29616
|
};
|
|
29617
|
+
var ye = (s = "", n = "") => {
|
|
29618
|
+
const t = `
|
|
29619
|
+
${s}
|
|
29620
|
+
`.split(`
|
|
29621
|
+
`), i = T2(n).length, r2 = Math.max(t.reduce((o, l2) => {
|
|
29622
|
+
const $2 = T2(l2);
|
|
29623
|
+
return $2.length > o ? $2.length : o;
|
|
29624
|
+
}, 0), i) + 2, c2 = t.map((o) => `${import_picocolors2.default.gray(a)} ${import_picocolors2.default.dim(o)}${" ".repeat(r2 - T2(o).length)}${import_picocolors2.default.gray(a)}`).join(`
|
|
29625
|
+
`);
|
|
29626
|
+
process.stdout.write(`${import_picocolors2.default.gray(a)}
|
|
29627
|
+
${import_picocolors2.default.green(S2)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(N2.repeat(Math.max(r2 - i - 1, 1)) + re)}
|
|
29628
|
+
${c2}
|
|
29629
|
+
${import_picocolors2.default.gray(ie + N2.repeat(r2 + 2) + ne)}
|
|
29630
|
+
`);
|
|
29631
|
+
};
|
|
29455
29632
|
var ve = (s = "") => {
|
|
29456
29633
|
process.stdout.write(`${import_picocolors2.default.gray(m2)} ${import_picocolors2.default.red(s)}
|
|
29457
29634
|
|
|
@@ -29593,6 +29770,45 @@ async function runSurvey() {
|
|
|
29593
29770
|
});
|
|
29594
29771
|
if (BD(email))
|
|
29595
29772
|
cancel();
|
|
29773
|
+
ye(`The host runs a private Docker registry behind Caddy.
|
|
29774
|
+
Create an A record for the registry domain pointing to your host before deploy.`, "Registry");
|
|
29775
|
+
const registryDomain = await ue({
|
|
29776
|
+
message: "Registry domain (full FQDN)",
|
|
29777
|
+
placeholder: "registry.example.com",
|
|
29778
|
+
validate: (v2) => /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(v2) ? undefined : "Expected a domain"
|
|
29779
|
+
});
|
|
29780
|
+
if (BD(registryDomain))
|
|
29781
|
+
cancel();
|
|
29782
|
+
const registryUser = await ue({
|
|
29783
|
+
message: "Registry username",
|
|
29784
|
+
initialValue: "deploy",
|
|
29785
|
+
validate: (v2) => /^[a-z_][a-z0-9_-]*$/.test(v2) ? undefined : "Invalid username"
|
|
29786
|
+
});
|
|
29787
|
+
if (BD(registryUser))
|
|
29788
|
+
cancel();
|
|
29789
|
+
const registryPasswordEnv = await ue({
|
|
29790
|
+
message: "Env var holding the registry password",
|
|
29791
|
+
initialValue: "ARC_REGISTRY_PASSWORD",
|
|
29792
|
+
validate: (v2) => /^[A-Z][A-Z0-9_]*$/.test(v2) ? undefined : "Must be UPPER_SNAKE_CASE"
|
|
29793
|
+
});
|
|
29794
|
+
if (BD(registryPasswordEnv))
|
|
29795
|
+
cancel();
|
|
29796
|
+
const generatePassword = await me({
|
|
29797
|
+
message: "Generate a random password now?",
|
|
29798
|
+
initialValue: true
|
|
29799
|
+
});
|
|
29800
|
+
if (BD(generatePassword))
|
|
29801
|
+
cancel();
|
|
29802
|
+
if (generatePassword) {
|
|
29803
|
+
const random = generateRandomPassword(32);
|
|
29804
|
+
ye(`Save this password \u2014 you'll need it on every deploy.
|
|
29805
|
+
|
|
29806
|
+
export ${registryPasswordEnv}=${random}
|
|
29807
|
+
|
|
29808
|
+
This prompt is the only time it's shown.`, "Registry password");
|
|
29809
|
+
} else {
|
|
29810
|
+
ye(`Set ${registryPasswordEnv} in your shell before running deploy.`, "Registry password");
|
|
29811
|
+
}
|
|
29596
29812
|
fe("Configuration ready \u2014 writing deploy.arc.json");
|
|
29597
29813
|
return {
|
|
29598
29814
|
target: {
|
|
@@ -29603,9 +29819,19 @@ async function runSurvey() {
|
|
|
29603
29819
|
},
|
|
29604
29820
|
envs,
|
|
29605
29821
|
caddy: { email },
|
|
29822
|
+
registry: {
|
|
29823
|
+
domain: registryDomain,
|
|
29824
|
+
username: registryUser,
|
|
29825
|
+
passwordEnv: registryPasswordEnv
|
|
29826
|
+
},
|
|
29606
29827
|
provision
|
|
29607
29828
|
};
|
|
29608
29829
|
}
|
|
29830
|
+
function generateRandomPassword(bytes) {
|
|
29831
|
+
const buf = new Uint8Array(bytes);
|
|
29832
|
+
crypto.getRandomValues(buf);
|
|
29833
|
+
return btoa(String.fromCharCode(...buf)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
29834
|
+
}
|
|
29609
29835
|
function cancel() {
|
|
29610
29836
|
ve("Cancelled.");
|
|
29611
29837
|
process.exit(0);
|
|
@@ -29626,15 +29852,43 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
29626
29852
|
err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
|
|
29627
29853
|
process.exit(1);
|
|
29628
29854
|
})() : Object.keys(cfg.envs);
|
|
29629
|
-
const manifestPath =
|
|
29630
|
-
|
|
29631
|
-
|
|
29632
|
-
|
|
29633
|
-
|
|
29634
|
-
|
|
29635
|
-
|
|
29636
|
-
|
|
29637
|
-
|
|
29855
|
+
const manifestPath = join17(ws.modulesDir, "manifest.json");
|
|
29856
|
+
if (!options.imageTag) {
|
|
29857
|
+
const needBuild = options.rebuild || !existsSync14(manifestPath);
|
|
29858
|
+
if (needBuild && !options.skipBuild) {
|
|
29859
|
+
log2("Building platform...");
|
|
29860
|
+
await buildAll(ws, { noCache: options.rebuild });
|
|
29861
|
+
ok("Build complete");
|
|
29862
|
+
} else if (!existsSync14(manifestPath)) {
|
|
29863
|
+
err("No build found and --skip-build was set.");
|
|
29864
|
+
process.exit(1);
|
|
29865
|
+
}
|
|
29866
|
+
}
|
|
29867
|
+
const imageName = sanitizeImageName(ws.rootPkg.name ?? ws.appName);
|
|
29868
|
+
let fullRef;
|
|
29869
|
+
let contentHash;
|
|
29870
|
+
if (options.imageTag) {
|
|
29871
|
+
contentHash = options.imageTag.includes(":") ? options.imageTag.split(":").pop() : options.imageTag;
|
|
29872
|
+
fullRef = `${cfg.registry.domain}/${imageName}:${contentHash}`;
|
|
29873
|
+
log2(`Pinning to existing image ${fullRef} (skipping build + push)`);
|
|
29874
|
+
} else {
|
|
29875
|
+
log2(`Building Docker image ${imageName}...`);
|
|
29876
|
+
const result = await buildImage(ws, {
|
|
29877
|
+
imageName,
|
|
29878
|
+
registryDomain: cfg.registry.domain
|
|
29879
|
+
});
|
|
29880
|
+
fullRef = result.fullRef;
|
|
29881
|
+
contentHash = result.contentHash;
|
|
29882
|
+
ok(`Image built: ${fullRef}`);
|
|
29883
|
+
if (options.buildOnly) {
|
|
29884
|
+
log2(`contentHash: ${contentHash}`);
|
|
29885
|
+
return;
|
|
29886
|
+
}
|
|
29887
|
+
log2(`Logging in to ${cfg.registry.domain}...`);
|
|
29888
|
+
await dockerLogin(cfg.registry);
|
|
29889
|
+
log2(`Pushing ${fullRef}...`);
|
|
29890
|
+
await dockerPush(fullRef);
|
|
29891
|
+
ok("Image pushed");
|
|
29638
29892
|
}
|
|
29639
29893
|
log2("Inspecting remote server...");
|
|
29640
29894
|
const state = await detectRemoteState(cfg);
|
|
@@ -29651,58 +29905,53 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
29651
29905
|
});
|
|
29652
29906
|
}
|
|
29653
29907
|
for (const env2 of targetEnvs) {
|
|
29654
|
-
log2(`
|
|
29655
|
-
const outcome = await
|
|
29908
|
+
log2(`Updating env "${env2}"...`);
|
|
29909
|
+
const outcome = await updateEnvDeployment({
|
|
29910
|
+
target: cfg.target,
|
|
29656
29911
|
cfg,
|
|
29657
29912
|
env: env2,
|
|
29658
|
-
|
|
29659
|
-
projectDir: ws.rootDir
|
|
29913
|
+
fullRef
|
|
29660
29914
|
});
|
|
29661
|
-
if (outcome.
|
|
29662
|
-
ok(`${env2}:
|
|
29915
|
+
if (outcome.redeployed) {
|
|
29916
|
+
ok(`${env2}: live at ${fullRef}`);
|
|
29663
29917
|
} else {
|
|
29664
|
-
|
|
29665
|
-
if (outcome.changedModules.length > 0) {
|
|
29666
|
-
parts.push(`${outcome.changedModules.length} module(s): ${outcome.changedModules.join(", ")}`);
|
|
29667
|
-
}
|
|
29668
|
-
if (outcome.shellChanged)
|
|
29669
|
-
parts.push("shell");
|
|
29670
|
-
if (outcome.stylesChanged)
|
|
29671
|
-
parts.push("styles");
|
|
29672
|
-
ok(`${env2}: updated ${parts.join(", ")}`);
|
|
29918
|
+
err(`${env2}: deployed but health check did not pass within retries \u2014 check \`docker logs arc-${env2}\``);
|
|
29673
29919
|
}
|
|
29674
29920
|
}
|
|
29675
29921
|
}
|
|
29676
29922
|
function readCliVersion() {
|
|
29677
|
-
const candidates = [];
|
|
29678
|
-
const entry = process.argv[1];
|
|
29679
|
-
if (entry) {
|
|
29680
|
-
candidates.push(join18(dirname7(entry), "..", "package.json"));
|
|
29681
|
-
}
|
|
29682
29923
|
try {
|
|
29683
|
-
|
|
29684
|
-
|
|
29685
|
-
|
|
29686
|
-
|
|
29687
|
-
|
|
29688
|
-
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29924
|
+
let cur = dirname9(fileURLToPath7(import.meta.url));
|
|
29925
|
+
const root = dirname9(cur).startsWith("/") ? "/" : ".";
|
|
29926
|
+
while (cur !== root && cur !== "") {
|
|
29927
|
+
const candidate = join17(cur, "package.json");
|
|
29928
|
+
if (existsSync14(candidate)) {
|
|
29929
|
+
const pkg = JSON.parse(readFileSync13(candidate, "utf-8"));
|
|
29930
|
+
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
29931
|
+
return pkg.version ?? "unknown";
|
|
29932
|
+
}
|
|
29933
|
+
}
|
|
29934
|
+
const parent = dirname9(cur);
|
|
29935
|
+
if (parent === cur)
|
|
29936
|
+
break;
|
|
29937
|
+
cur = parent;
|
|
29938
|
+
}
|
|
29939
|
+
return "unknown";
|
|
29940
|
+
} catch {
|
|
29941
|
+
return "unknown";
|
|
29692
29942
|
}
|
|
29693
|
-
return "unknown";
|
|
29694
29943
|
}
|
|
29695
29944
|
async function hashDeployConfig(rootDir) {
|
|
29696
|
-
const p2 =
|
|
29697
|
-
const content =
|
|
29945
|
+
const p2 = join17(rootDir, "deploy.arc.json");
|
|
29946
|
+
const content = readFileSync13(p2);
|
|
29698
29947
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
29699
29948
|
hasher.update(content);
|
|
29700
29949
|
return hasher.digest("hex").slice(0, 16);
|
|
29701
29950
|
}
|
|
29702
29951
|
|
|
29703
|
-
// src/
|
|
29704
|
-
import { existsSync as
|
|
29705
|
-
import { join as
|
|
29952
|
+
// src/platform/startup.ts
|
|
29953
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, watch } from "fs";
|
|
29954
|
+
import { join as join19 } from "path";
|
|
29706
29955
|
|
|
29707
29956
|
// ../host/src/create-server.ts
|
|
29708
29957
|
var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
|
|
@@ -30162,8 +30411,8 @@ var d2 = class {
|
|
|
30162
30411
|
throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part (" + this[e2].length + ")");
|
|
30163
30412
|
if (c2 > w2)
|
|
30164
30413
|
throw new TypeError("CronPattern: From value is larger than to value: '" + t + "'");
|
|
30165
|
-
for (let
|
|
30166
|
-
this.setPart(e2,
|
|
30414
|
+
for (let T3 = c2;T3 <= w2; T3 += C2)
|
|
30415
|
+
this.setPart(e2, T3, i[1] || s);
|
|
30167
30416
|
}
|
|
30168
30417
|
extractNth(t, e2) {
|
|
30169
30418
|
let r2 = t, s;
|
|
@@ -30948,8 +31197,8 @@ function querySubscriptionHandler() {
|
|
|
30948
31197
|
subscriptionId,
|
|
30949
31198
|
data: data ?? null
|
|
30950
31199
|
});
|
|
30951
|
-
} catch (
|
|
30952
|
-
console.error(`[Arc] Query subscription error:`,
|
|
31200
|
+
} catch (err2) {
|
|
31201
|
+
console.error(`[Arc] Query subscription error:`, err2);
|
|
30953
31202
|
}
|
|
30954
31203
|
};
|
|
30955
31204
|
sendData();
|
|
@@ -31152,231 +31401,8 @@ async function createArcServer(config) {
|
|
|
31152
31401
|
};
|
|
31153
31402
|
}
|
|
31154
31403
|
// src/platform/server.ts
|
|
31155
|
-
import { existsSync as
|
|
31156
|
-
import { join as
|
|
31157
|
-
|
|
31158
|
-
// src/platform/deploy-api.ts
|
|
31159
|
-
var {spawn: spawn4 } = globalThis.Bun;
|
|
31160
|
-
import {
|
|
31161
|
-
cpSync,
|
|
31162
|
-
existsSync as existsSync15,
|
|
31163
|
-
mkdirSync as mkdirSync14,
|
|
31164
|
-
readFileSync as readFileSync15,
|
|
31165
|
-
rmSync as rmSync4,
|
|
31166
|
-
writeFileSync as writeFileSync15
|
|
31167
|
-
} from "fs";
|
|
31168
|
-
import { dirname as dirname8, join as join19, normalize as normalize2, resolve } from "path";
|
|
31169
|
-
function createDeployApiHandler(opts) {
|
|
31170
|
-
return async (req, url, ctx) => {
|
|
31171
|
-
const p3 = url.pathname;
|
|
31172
|
-
if (!p3.startsWith("/api/deploy/"))
|
|
31173
|
-
return null;
|
|
31174
|
-
const cors = ctx.corsHeaders;
|
|
31175
|
-
if (p3 === "/api/deploy/health" && req.method === "GET") {
|
|
31176
|
-
return Response.json({ ok: true, modules: opts.getManifest().modules.length }, { headers: cors });
|
|
31177
|
-
}
|
|
31178
|
-
if (p3 === "/api/deploy/framework" && req.method === "GET") {
|
|
31179
|
-
const hashPath = join19(opts.ws.arcDir, ".deps-hash");
|
|
31180
|
-
const depsHash = existsSync15(hashPath) ? readFileSync15(hashPath, "utf-8").trim() : null;
|
|
31181
|
-
return Response.json({ depsHash }, { headers: cors });
|
|
31182
|
-
}
|
|
31183
|
-
if (p3 === "/api/deploy/manifest" && req.method === "GET") {
|
|
31184
|
-
return Response.json(opts.getManifest(), { headers: cors });
|
|
31185
|
-
}
|
|
31186
|
-
if (p3 === "/api/deploy/framework" && req.method === "POST") {
|
|
31187
|
-
const form = await req.formData();
|
|
31188
|
-
const pkgFile = form.get("package.json");
|
|
31189
|
-
const lockFile = form.get("bun.lock");
|
|
31190
|
-
if (!isFile(pkgFile) || !isFile(lockFile)) {
|
|
31191
|
-
return badRequest("framework requires package.json + bun.lock", cors);
|
|
31192
|
-
}
|
|
31193
|
-
mkdirSync14(opts.ws.arcDir, { recursive: true });
|
|
31194
|
-
writeFileSync15(join19(opts.ws.arcDir, "package.json"), Buffer.from(await pkgFile.arrayBuffer()));
|
|
31195
|
-
writeFileSync15(join19(opts.ws.arcDir, "bun.lock"), Buffer.from(await lockFile.arrayBuffer()));
|
|
31196
|
-
const start = Date.now();
|
|
31197
|
-
const installOk = await runBun(["install", "--production"], opts.ws.arcDir);
|
|
31198
|
-
if (!installOk) {
|
|
31199
|
-
return Response.json({ error: "bun install failed" }, { status: 500, headers: cors });
|
|
31200
|
-
}
|
|
31201
|
-
const cliBin = process.argv[1] ?? "arc";
|
|
31202
|
-
const shellOk = await runBun([
|
|
31203
|
-
"run",
|
|
31204
|
-
cliBin,
|
|
31205
|
-
"_build-shell",
|
|
31206
|
-
"--out",
|
|
31207
|
-
opts.ws.shellDir,
|
|
31208
|
-
"--from",
|
|
31209
|
-
join19(opts.ws.arcDir, "node_modules")
|
|
31210
|
-
], opts.ws.arcDir);
|
|
31211
|
-
if (!shellOk) {
|
|
31212
|
-
return Response.json({ error: "shell build failed" }, { status: 500, headers: cors });
|
|
31213
|
-
}
|
|
31214
|
-
const newHash = sha256Hex(Buffer.concat([
|
|
31215
|
-
readFileSync15(join19(opts.ws.arcDir, "package.json")),
|
|
31216
|
-
readFileSync15(join19(opts.ws.arcDir, "bun.lock"))
|
|
31217
|
-
]));
|
|
31218
|
-
writeFileSync15(join19(opts.ws.arcDir, ".deps-hash"), newHash + `
|
|
31219
|
-
`);
|
|
31220
|
-
return Response.json({
|
|
31221
|
-
changed: true,
|
|
31222
|
-
tookMs: Date.now() - start,
|
|
31223
|
-
needsRestart: true
|
|
31224
|
-
}, { headers: cors });
|
|
31225
|
-
}
|
|
31226
|
-
const moduleMatch = p3.match(/^\/api\/deploy\/modules\/([^/]+)$/);
|
|
31227
|
-
if (moduleMatch && req.method === "POST") {
|
|
31228
|
-
const safeName = sanitizeName2(moduleMatch[1]);
|
|
31229
|
-
if (!safeName)
|
|
31230
|
-
return badRequest("invalid module name", cors);
|
|
31231
|
-
const form = await req.formData();
|
|
31232
|
-
const browser = form.get("browser.js");
|
|
31233
|
-
if (!isFile(browser)) {
|
|
31234
|
-
return badRequest("modules/<name> requires browser.js", cors);
|
|
31235
|
-
}
|
|
31236
|
-
const stagingDir = join19(opts.ws.arcDir, ".staging", "modules", safeName);
|
|
31237
|
-
mkdirSync14(stagingDir, { recursive: true });
|
|
31238
|
-
await writeField(stagingDir, "browser.js", browser);
|
|
31239
|
-
const server = form.get("server.js");
|
|
31240
|
-
if (isFile(server)) {
|
|
31241
|
-
await writeField(stagingDir, "server.js", server);
|
|
31242
|
-
}
|
|
31243
|
-
const pkg = form.get("package.json");
|
|
31244
|
-
if (isFile(pkg)) {
|
|
31245
|
-
await writeField(stagingDir, "package.json", pkg);
|
|
31246
|
-
}
|
|
31247
|
-
const access = form.get("access.json");
|
|
31248
|
-
if (isFile(access)) {
|
|
31249
|
-
await writeField(stagingDir, "access.json", access);
|
|
31250
|
-
}
|
|
31251
|
-
const stagingPkgPath = join19(stagingDir, "package.json");
|
|
31252
|
-
const livePkgPath = join19(opts.ws.arcDir, "modules", safeName, "package.json");
|
|
31253
|
-
const stagingPkgBytes = existsSync15(stagingPkgPath) ? readFileSync15(stagingPkgPath) : Buffer.from("");
|
|
31254
|
-
const livePkgBytes = existsSync15(livePkgPath) ? readFileSync15(livePkgPath) : Buffer.from("");
|
|
31255
|
-
const depsChanged = sha256Hex(stagingPkgBytes) !== sha256Hex(livePkgBytes);
|
|
31256
|
-
writeFileSync15(join19(stagingDir, ".deps-hash"), sha256Hex(stagingPkgBytes) + `
|
|
31257
|
-
`);
|
|
31258
|
-
if (depsChanged && existsSync15(stagingPkgPath)) {
|
|
31259
|
-
const ok2 = await runBun(["install", "--production"], stagingDir);
|
|
31260
|
-
if (!ok2) {
|
|
31261
|
-
rmSync4(stagingDir, { recursive: true, force: true });
|
|
31262
|
-
return Response.json({ error: `bun install failed for module ${safeName}` }, { status: 500, headers: cors });
|
|
31263
|
-
}
|
|
31264
|
-
} else if (!depsChanged) {
|
|
31265
|
-
const liveNodeModules = join19(opts.ws.arcDir, "modules", safeName, "node_modules");
|
|
31266
|
-
if (existsSync15(liveNodeModules)) {
|
|
31267
|
-
cpSync(liveNodeModules, join19(stagingDir, "node_modules"), {
|
|
31268
|
-
recursive: true
|
|
31269
|
-
});
|
|
31270
|
-
}
|
|
31271
|
-
}
|
|
31272
|
-
return Response.json({
|
|
31273
|
-
ok: true,
|
|
31274
|
-
name: safeName,
|
|
31275
|
-
depsChanged,
|
|
31276
|
-
serverIncluded: isFile(server)
|
|
31277
|
-
}, { headers: cors });
|
|
31278
|
-
}
|
|
31279
|
-
if (p3 === "/api/deploy/styles" && req.method === "POST") {
|
|
31280
|
-
const form = await req.formData();
|
|
31281
|
-
const stagingDir = join19(opts.ws.arcDir, ".staging");
|
|
31282
|
-
mkdirSync14(stagingDir, { recursive: true });
|
|
31283
|
-
const written = [];
|
|
31284
|
-
for (const name of ["styles.css", "theme.css"]) {
|
|
31285
|
-
const f2 = form.get(name);
|
|
31286
|
-
if (isFile(f2)) {
|
|
31287
|
-
await writeField(stagingDir, name, f2);
|
|
31288
|
-
written.push(name);
|
|
31289
|
-
}
|
|
31290
|
-
}
|
|
31291
|
-
return Response.json({ ok: true, written }, { headers: cors });
|
|
31292
|
-
}
|
|
31293
|
-
if (p3 === "/api/deploy/manifest" && req.method === "POST") {
|
|
31294
|
-
const body = await req.json();
|
|
31295
|
-
if (!validateManifest(body)) {
|
|
31296
|
-
return badRequest("invalid manifest body", cors);
|
|
31297
|
-
}
|
|
31298
|
-
const stagingRoot = join19(opts.ws.arcDir, ".staging");
|
|
31299
|
-
const stagingModules = join19(stagingRoot, "modules");
|
|
31300
|
-
let serverSideChanged = false;
|
|
31301
|
-
if (existsSync15(stagingModules)) {
|
|
31302
|
-
const stagedNames = readDir(stagingModules);
|
|
31303
|
-
for (const name of stagedNames) {
|
|
31304
|
-
const src2 = join19(stagingModules, name);
|
|
31305
|
-
const dst = join19(opts.ws.arcDir, "modules", name);
|
|
31306
|
-
if (existsSync15(join19(src2, "server.js")))
|
|
31307
|
-
serverSideChanged = true;
|
|
31308
|
-
if (existsSync15(dst)) {
|
|
31309
|
-
rmSync4(dst, { recursive: true, force: true });
|
|
31310
|
-
}
|
|
31311
|
-
mkdirSync14(dirname8(dst), { recursive: true });
|
|
31312
|
-
cpSync(src2, dst, { recursive: true });
|
|
31313
|
-
}
|
|
31314
|
-
}
|
|
31315
|
-
for (const name of ["styles.css", "theme.css"]) {
|
|
31316
|
-
const src2 = join19(stagingRoot, name);
|
|
31317
|
-
if (existsSync15(src2)) {
|
|
31318
|
-
cpSync(src2, join19(opts.ws.arcDir, name));
|
|
31319
|
-
}
|
|
31320
|
-
}
|
|
31321
|
-
writeFileSync15(join19(opts.ws.modulesDir, "manifest.json"), JSON.stringify(body, null, 2));
|
|
31322
|
-
opts.setManifest(body);
|
|
31323
|
-
rmSync4(stagingRoot, { recursive: true, force: true });
|
|
31324
|
-
if (!serverSideChanged) {
|
|
31325
|
-
opts.notifyReload(body);
|
|
31326
|
-
}
|
|
31327
|
-
return Response.json({
|
|
31328
|
-
ok: true,
|
|
31329
|
-
moduleCount: body.modules.length,
|
|
31330
|
-
needsRestart: serverSideChanged
|
|
31331
|
-
}, { headers: cors });
|
|
31332
|
-
}
|
|
31333
|
-
return new Response("Not Found", { status: 404, headers: cors });
|
|
31334
|
-
};
|
|
31335
|
-
}
|
|
31336
|
-
function badRequest(msg, cors) {
|
|
31337
|
-
return Response.json({ error: msg }, { status: 400, headers: cors });
|
|
31338
|
-
}
|
|
31339
|
-
function isFile(v3) {
|
|
31340
|
-
return typeof v3 === "object" && v3 !== null && typeof v3.arrayBuffer === "function" && typeof v3.name === "string";
|
|
31341
|
-
}
|
|
31342
|
-
async function writeField(targetDir, name, file) {
|
|
31343
|
-
const safe = normalize2(name);
|
|
31344
|
-
const safeRoot = resolve(targetDir);
|
|
31345
|
-
const full = resolve(safeRoot, safe);
|
|
31346
|
-
if (!full.startsWith(safeRoot + "/") && full !== safeRoot) {
|
|
31347
|
-
throw new Error(`Path traversal rejected: ${name}`);
|
|
31348
|
-
}
|
|
31349
|
-
mkdirSync14(dirname8(full), { recursive: true });
|
|
31350
|
-
writeFileSync15(full, Buffer.from(await file.arrayBuffer()));
|
|
31351
|
-
}
|
|
31352
|
-
function sanitizeName2(name) {
|
|
31353
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(name))
|
|
31354
|
-
return null;
|
|
31355
|
-
return name;
|
|
31356
|
-
}
|
|
31357
|
-
function readDir(dir) {
|
|
31358
|
-
if (!existsSync15(dir))
|
|
31359
|
-
return [];
|
|
31360
|
-
return __require("fs").readdirSync(dir);
|
|
31361
|
-
}
|
|
31362
|
-
async function runBun(args, cwd) {
|
|
31363
|
-
const proc2 = spawn4({
|
|
31364
|
-
cmd: ["bun", ...args],
|
|
31365
|
-
cwd,
|
|
31366
|
-
stdout: "inherit",
|
|
31367
|
-
stderr: "inherit"
|
|
31368
|
-
});
|
|
31369
|
-
const exit = await proc2.exited;
|
|
31370
|
-
return exit === 0;
|
|
31371
|
-
}
|
|
31372
|
-
function validateManifest(m4) {
|
|
31373
|
-
if (!m4 || typeof m4 !== "object")
|
|
31374
|
-
return false;
|
|
31375
|
-
const cast = m4;
|
|
31376
|
-
return Array.isArray(cast.modules) && typeof cast.shellHash === "string" && typeof cast.stylesHash === "string" && typeof cast.buildTime === "string";
|
|
31377
|
-
}
|
|
31378
|
-
|
|
31379
|
-
// src/platform/server.ts
|
|
31404
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync14 } from "fs";
|
|
31405
|
+
import { join as join18 } from "path";
|
|
31380
31406
|
function generateShellHtml(appName, manifest, arcEntries) {
|
|
31381
31407
|
const arcImports = {};
|
|
31382
31408
|
if (arcEntries) {
|
|
@@ -31440,7 +31466,7 @@ function getMime(path4) {
|
|
|
31440
31466
|
return MIME[ext2] ?? "application/octet-stream";
|
|
31441
31467
|
}
|
|
31442
31468
|
function serveFile(filePath, headers = {}) {
|
|
31443
|
-
if (!
|
|
31469
|
+
if (!existsSync15(filePath))
|
|
31444
31470
|
return new Response("Not Found", { status: 404 });
|
|
31445
31471
|
return new Response(Bun.file(filePath), {
|
|
31446
31472
|
headers: { "Content-Type": getMime(filePath), ...headers }
|
|
@@ -31448,20 +31474,20 @@ function serveFile(filePath, headers = {}) {
|
|
|
31448
31474
|
}
|
|
31449
31475
|
var MODULE_SIG_SECRET = process.env.ARC_MODULE_SECRET ?? crypto.randomUUID();
|
|
31450
31476
|
var MODULE_SIG_TTL = 3600;
|
|
31451
|
-
function
|
|
31477
|
+
function signChunkUrl(chunk, file) {
|
|
31452
31478
|
const exp = Math.floor(Date.now() / 1000) + MODULE_SIG_TTL;
|
|
31453
31479
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
31454
|
-
hasher.update(`${
|
|
31480
|
+
hasher.update(`${chunk}/${file}:${exp}:${MODULE_SIG_SECRET}`);
|
|
31455
31481
|
const sig = hasher.digest("hex").slice(0, 16);
|
|
31456
|
-
return `/modules/${
|
|
31482
|
+
return `/modules/${chunk}/${file}?sig=${sig}&exp=${exp}`;
|
|
31457
31483
|
}
|
|
31458
|
-
function
|
|
31484
|
+
function verifyChunkSignature(chunk, file, sig, exp) {
|
|
31459
31485
|
if (!sig || !exp)
|
|
31460
31486
|
return false;
|
|
31461
31487
|
if (Number(exp) < Date.now() / 1000)
|
|
31462
31488
|
return false;
|
|
31463
31489
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
31464
|
-
hasher.update(`${
|
|
31490
|
+
hasher.update(`${chunk}/${file}:${exp}:${MODULE_SIG_SECRET}`);
|
|
31465
31491
|
return hasher.digest("hex").slice(0, 16) === sig;
|
|
31466
31492
|
}
|
|
31467
31493
|
function decodeTokenPayload(jwt2) {
|
|
@@ -31496,70 +31522,90 @@ function parseArcTokensHeader(header) {
|
|
|
31496
31522
|
return payloads;
|
|
31497
31523
|
}
|
|
31498
31524
|
async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
|
|
31525
|
+
const allowedChunks = new Set(["public"]);
|
|
31526
|
+
for (const t of tokenPayloads) {
|
|
31527
|
+
if (t?.tokenType)
|
|
31528
|
+
allowedChunks.add(t.tokenType);
|
|
31529
|
+
}
|
|
31499
31530
|
const filtered = [];
|
|
31500
31531
|
for (const mod of manifest.modules) {
|
|
31501
|
-
|
|
31502
|
-
|
|
31532
|
+
if (!allowedChunks.has(mod.chunk))
|
|
31533
|
+
continue;
|
|
31534
|
+
if (mod.chunk === "public") {
|
|
31503
31535
|
filtered.push(mod);
|
|
31504
31536
|
continue;
|
|
31505
31537
|
}
|
|
31506
|
-
|
|
31507
|
-
|
|
31508
|
-
|
|
31509
|
-
|
|
31510
|
-
const
|
|
31511
|
-
|
|
31538
|
+
const access = moduleAccessMap.get(mod.name);
|
|
31539
|
+
let granted = true;
|
|
31540
|
+
if (access && access.rules.length > 0) {
|
|
31541
|
+
granted = false;
|
|
31542
|
+
for (const rule of access.rules) {
|
|
31543
|
+
if (rule.token.name !== mod.chunk)
|
|
31544
|
+
continue;
|
|
31545
|
+
const matching = tokenPayloads.find((t) => t.tokenType === rule.token.name);
|
|
31546
|
+
if (!matching)
|
|
31547
|
+
continue;
|
|
31512
31548
|
granted = rule.check ? await rule.check(matching) : true;
|
|
31513
31549
|
if (granted)
|
|
31514
31550
|
break;
|
|
31515
31551
|
}
|
|
31516
31552
|
}
|
|
31517
31553
|
if (granted) {
|
|
31518
|
-
filtered.push({ ...mod, url:
|
|
31554
|
+
filtered.push({ ...mod, url: signChunkUrl(mod.chunk, mod.file) });
|
|
31519
31555
|
}
|
|
31520
31556
|
}
|
|
31521
31557
|
return {
|
|
31522
31558
|
modules: filtered,
|
|
31559
|
+
chunks: manifest.chunks,
|
|
31523
31560
|
shellHash: manifest.shellHash,
|
|
31524
31561
|
stylesHash: manifest.stylesHash,
|
|
31525
31562
|
buildTime: manifest.buildTime
|
|
31526
31563
|
};
|
|
31527
31564
|
}
|
|
31528
|
-
|
|
31565
|
+
var CHUNK_NAME_RE = /^[A-Za-z0-9_-]+$/;
|
|
31566
|
+
var MODULE_FILE_RE = /^[A-Za-z0-9_.-]+\.js$/;
|
|
31567
|
+
function staticFilesHandler(ws, devMode) {
|
|
31529
31568
|
return (_req, url, ctx) => {
|
|
31530
31569
|
const path4 = url.pathname;
|
|
31531
31570
|
if (path4.startsWith("/shell/"))
|
|
31532
|
-
return serveFile(
|
|
31571
|
+
return serveFile(join18(ws.shellDir, path4.slice(7)), ctx.corsHeaders);
|
|
31533
31572
|
if (path4.startsWith("/modules/")) {
|
|
31534
|
-
const
|
|
31535
|
-
const
|
|
31536
|
-
|
|
31537
|
-
|
|
31573
|
+
const rest = path4.slice(9);
|
|
31574
|
+
const slash = rest.indexOf("/");
|
|
31575
|
+
if (slash <= 0) {
|
|
31576
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
31577
|
+
}
|
|
31578
|
+
const chunk = rest.slice(0, slash);
|
|
31579
|
+
const file = rest.slice(slash + 1);
|
|
31580
|
+
if (!CHUNK_NAME_RE.test(chunk) || !MODULE_FILE_RE.test(file)) {
|
|
31581
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
31582
|
+
}
|
|
31583
|
+
if (chunk !== "public") {
|
|
31538
31584
|
const sig = url.searchParams.get("sig");
|
|
31539
31585
|
const exp = url.searchParams.get("exp");
|
|
31540
|
-
if (!
|
|
31586
|
+
if (!verifyChunkSignature(chunk, file, sig, exp)) {
|
|
31541
31587
|
return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
|
|
31542
31588
|
}
|
|
31543
31589
|
}
|
|
31544
|
-
return serveFile(
|
|
31590
|
+
return serveFile(join18(ws.modulesDir, chunk, file), {
|
|
31545
31591
|
...ctx.corsHeaders,
|
|
31546
31592
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
31547
31593
|
});
|
|
31548
31594
|
}
|
|
31549
31595
|
if (path4.startsWith("/locales/"))
|
|
31550
|
-
return serveFile(
|
|
31596
|
+
return serveFile(join18(ws.arcDir, path4.slice(1)), ctx.corsHeaders);
|
|
31551
31597
|
if (path4.startsWith("/assets/"))
|
|
31552
|
-
return serveFile(
|
|
31598
|
+
return serveFile(join18(ws.assetsDir, path4.slice(8)), ctx.corsHeaders);
|
|
31553
31599
|
if (path4 === "/styles.css")
|
|
31554
|
-
return serveFile(
|
|
31600
|
+
return serveFile(join18(ws.arcDir, "styles.css"), ctx.corsHeaders);
|
|
31555
31601
|
if (path4 === "/theme.css")
|
|
31556
|
-
return serveFile(
|
|
31602
|
+
return serveFile(join18(ws.arcDir, "theme.css"), ctx.corsHeaders);
|
|
31557
31603
|
if ((path4 === "/manifest.json" || path4 === "/manifest.webmanifest") && ws.manifest) {
|
|
31558
31604
|
return serveFile(ws.manifest.path, ctx.corsHeaders);
|
|
31559
31605
|
}
|
|
31560
31606
|
if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
|
|
31561
|
-
const publicFile =
|
|
31562
|
-
if (
|
|
31607
|
+
const publicFile = join18(ws.publicDir, path4.slice(1));
|
|
31608
|
+
if (existsSync15(publicFile))
|
|
31563
31609
|
return serveFile(publicFile, ctx.corsHeaders);
|
|
31564
31610
|
}
|
|
31565
31611
|
return null;
|
|
@@ -31645,13 +31691,6 @@ async function startPlatformServer(opts) {
|
|
|
31645
31691
|
}
|
|
31646
31692
|
}
|
|
31647
31693
|
};
|
|
31648
|
-
const deployApiEnabled = opts.deployApi ?? process.env.ARC_DEPLOY_API === "1";
|
|
31649
|
-
const deployApiHandler = deployApiEnabled ? createDeployApiHandler({
|
|
31650
|
-
ws,
|
|
31651
|
-
getManifest,
|
|
31652
|
-
setManifest,
|
|
31653
|
-
notifyReload
|
|
31654
|
-
}) : null;
|
|
31655
31694
|
if (!context) {
|
|
31656
31695
|
const cors = {
|
|
31657
31696
|
"Access-Control-Allow-Origin": "*",
|
|
@@ -31673,10 +31712,9 @@ async function startPlatformServer(opts) {
|
|
|
31673
31712
|
corsHeaders: cors
|
|
31674
31713
|
};
|
|
31675
31714
|
const handlers = [
|
|
31676
|
-
...deployApiHandler ? [deployApiHandler] : [],
|
|
31677
31715
|
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
|
|
31678
31716
|
devReloadHandler(sseClients),
|
|
31679
|
-
staticFilesHandler(ws, !!devMode
|
|
31717
|
+
staticFilesHandler(ws, !!devMode),
|
|
31680
31718
|
spaFallbackHandler(shellHtml)
|
|
31681
31719
|
];
|
|
31682
31720
|
for (const handler of handlers) {
|
|
@@ -31697,19 +31735,18 @@ async function startPlatformServer(opts) {
|
|
|
31697
31735
|
};
|
|
31698
31736
|
}
|
|
31699
31737
|
const { createBunSQLiteAdapterFactory: createBunSQLiteAdapterFactory2 } = await Promise.resolve().then(() => (init_dist(), exports_dist2));
|
|
31700
|
-
const dbPath = opts.dbPath ||
|
|
31738
|
+
const dbPath = opts.dbPath || join18(ws.arcDir, "data", "arc.db");
|
|
31701
31739
|
const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
31702
31740
|
if (dbDir)
|
|
31703
|
-
|
|
31741
|
+
mkdirSync14(dbDir, { recursive: true });
|
|
31704
31742
|
const arcServer = await createArcServer({
|
|
31705
31743
|
context,
|
|
31706
31744
|
dbAdapterFactory: createBunSQLiteAdapterFactory2(dbPath),
|
|
31707
31745
|
port,
|
|
31708
31746
|
httpHandlers: [
|
|
31709
|
-
...deployApiHandler ? [deployApiHandler] : [],
|
|
31710
31747
|
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
|
|
31711
31748
|
devReloadHandler(sseClients),
|
|
31712
|
-
staticFilesHandler(ws, !!devMode
|
|
31749
|
+
staticFilesHandler(ws, !!devMode),
|
|
31713
31750
|
spaFallbackHandler(shellHtml)
|
|
31714
31751
|
],
|
|
31715
31752
|
onWsClose: (clientId) => cleanupClientSubs(clientId)
|
|
@@ -31724,13 +31761,24 @@ async function startPlatformServer(opts) {
|
|
|
31724
31761
|
};
|
|
31725
31762
|
}
|
|
31726
31763
|
|
|
31727
|
-
// src/
|
|
31728
|
-
async function
|
|
31729
|
-
const ws =
|
|
31730
|
-
const port = 5005;
|
|
31731
|
-
|
|
31764
|
+
// src/platform/startup.ts
|
|
31765
|
+
async function startPlatform(opts) {
|
|
31766
|
+
const { ws, devMode } = opts;
|
|
31767
|
+
const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
|
|
31768
|
+
const dbPath = opts.dbPath ?? join19(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
|
|
31769
|
+
let manifest;
|
|
31770
|
+
if (devMode) {
|
|
31771
|
+
manifest = await buildAll(ws);
|
|
31772
|
+
} else {
|
|
31773
|
+
const manifestPath = join19(ws.modulesDir, "manifest.json");
|
|
31774
|
+
if (!existsSync16(manifestPath)) {
|
|
31775
|
+
err("No build found. Run `arc platform build` first.");
|
|
31776
|
+
process.exit(1);
|
|
31777
|
+
}
|
|
31778
|
+
manifest = JSON.parse(readFileSync14(manifestPath, "utf-8"));
|
|
31779
|
+
}
|
|
31732
31780
|
log2("Loading server context...");
|
|
31733
|
-
const { context, moduleAccess } = await loadServerContext(ws
|
|
31781
|
+
const { context, moduleAccess } = await loadServerContext(ws);
|
|
31734
31782
|
if (context) {
|
|
31735
31783
|
ok("Context loaded");
|
|
31736
31784
|
} else {
|
|
@@ -31743,13 +31791,25 @@ async function platformDev(opts = {}) {
|
|
|
31743
31791
|
manifest,
|
|
31744
31792
|
context,
|
|
31745
31793
|
moduleAccess,
|
|
31746
|
-
dbPath
|
|
31747
|
-
devMode
|
|
31794
|
+
dbPath,
|
|
31795
|
+
devMode,
|
|
31748
31796
|
arcEntries
|
|
31749
31797
|
});
|
|
31750
31798
|
ok(`Server on http://localhost:${port}`);
|
|
31751
|
-
if (platform3.contextHandler)
|
|
31799
|
+
if (platform3.contextHandler) {
|
|
31752
31800
|
ok("Commands, queries, WebSocket \u2014 all on same port");
|
|
31801
|
+
}
|
|
31802
|
+
if (devMode) {
|
|
31803
|
+
attachDevWatcher(ws, platform3);
|
|
31804
|
+
}
|
|
31805
|
+
const cleanup = () => {
|
|
31806
|
+
platform3.stop();
|
|
31807
|
+
process.exit(0);
|
|
31808
|
+
};
|
|
31809
|
+
process.on("SIGTERM", cleanup);
|
|
31810
|
+
process.on("SIGINT", cleanup);
|
|
31811
|
+
}
|
|
31812
|
+
function attachDevWatcher(ws, platform3) {
|
|
31753
31813
|
log2("Watching for changes...");
|
|
31754
31814
|
let rebuildTimer = null;
|
|
31755
31815
|
let isRebuilding = false;
|
|
@@ -31762,10 +31822,10 @@ async function platformDev(opts = {}) {
|
|
|
31762
31822
|
isRebuilding = true;
|
|
31763
31823
|
log2("Rebuilding...");
|
|
31764
31824
|
try {
|
|
31765
|
-
|
|
31766
|
-
platform3.setManifest(
|
|
31767
|
-
platform3.notifyReload(
|
|
31768
|
-
ok(`Rebuilt \u2014 ${
|
|
31825
|
+
const next = await buildAll(ws);
|
|
31826
|
+
platform3.setManifest(next);
|
|
31827
|
+
platform3.notifyReload(next);
|
|
31828
|
+
ok(`Rebuilt \u2014 ${next.modules.length} module(s)`);
|
|
31769
31829
|
} catch (e2) {
|
|
31770
31830
|
console.error(`Rebuild failed: ${e2}`);
|
|
31771
31831
|
} finally {
|
|
@@ -31774,8 +31834,8 @@ async function platformDev(opts = {}) {
|
|
|
31774
31834
|
}, 300);
|
|
31775
31835
|
};
|
|
31776
31836
|
for (const pkg of ws.packages) {
|
|
31777
|
-
const srcDir =
|
|
31778
|
-
if (!
|
|
31837
|
+
const srcDir = join19(pkg.path, "src");
|
|
31838
|
+
if (!existsSync16(srcDir))
|
|
31779
31839
|
continue;
|
|
31780
31840
|
watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
31781
31841
|
if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
|
|
@@ -31785,91 +31845,26 @@ async function platformDev(opts = {}) {
|
|
|
31785
31845
|
triggerRebuild();
|
|
31786
31846
|
});
|
|
31787
31847
|
}
|
|
31788
|
-
const localesDir =
|
|
31789
|
-
if (
|
|
31848
|
+
const localesDir = join19(ws.rootDir, "locales");
|
|
31849
|
+
if (existsSync16(localesDir)) {
|
|
31790
31850
|
watch(localesDir, { recursive: false }, (_event, filename) => {
|
|
31791
31851
|
if (!filename?.endsWith(".po"))
|
|
31792
31852
|
return;
|
|
31793
31853
|
triggerRebuild();
|
|
31794
31854
|
});
|
|
31795
31855
|
}
|
|
31796
|
-
|
|
31797
|
-
|
|
31798
|
-
|
|
31799
|
-
|
|
31800
|
-
|
|
31801
|
-
|
|
31856
|
+
}
|
|
31857
|
+
|
|
31858
|
+
// src/commands/platform-dev.ts
|
|
31859
|
+
async function platformDev(opts = {}) {
|
|
31860
|
+
const ws = resolveWorkspace();
|
|
31861
|
+
await startPlatform({ ws, devMode: true });
|
|
31802
31862
|
}
|
|
31803
31863
|
|
|
31804
31864
|
// src/commands/platform-start.ts
|
|
31805
|
-
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
31806
|
-
import { join as join22 } from "path";
|
|
31807
31865
|
async function platformStart() {
|
|
31808
31866
|
const ws = resolveWorkspace();
|
|
31809
|
-
|
|
31810
|
-
const deployApi = process.env.ARC_DEPLOY_API === "1";
|
|
31811
|
-
const manifestPath = join22(ws.modulesDir, "manifest.json");
|
|
31812
|
-
if (!existsSync18(manifestPath)) {
|
|
31813
|
-
if (!deployApi) {
|
|
31814
|
-
err("No build found. Run `arc platform build` first.");
|
|
31815
|
-
process.exit(1);
|
|
31816
|
-
}
|
|
31817
|
-
log2("Pre-deploy mode \u2014 no manifest yet, awaiting first /api/deploy/*");
|
|
31818
|
-
const emptyManifest = {
|
|
31819
|
-
modules: [],
|
|
31820
|
-
shellHash: "",
|
|
31821
|
-
stylesHash: "",
|
|
31822
|
-
buildTime: new Date().toISOString()
|
|
31823
|
-
};
|
|
31824
|
-
const platform4 = await startPlatformServer({
|
|
31825
|
-
ws,
|
|
31826
|
-
port,
|
|
31827
|
-
manifest: emptyManifest,
|
|
31828
|
-
context: null,
|
|
31829
|
-
moduleAccess: new Map,
|
|
31830
|
-
dbPath: join22(ws.rootDir, ".arc", "data", "prod.db"),
|
|
31831
|
-
devMode: false,
|
|
31832
|
-
deployApi: true,
|
|
31833
|
-
arcEntries: []
|
|
31834
|
-
});
|
|
31835
|
-
ok(`Pre-deploy server on http://localhost:${port}`);
|
|
31836
|
-
registerSignalCleanup(platform4);
|
|
31837
|
-
return;
|
|
31838
|
-
}
|
|
31839
|
-
const manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
|
|
31840
|
-
log2("Loading server context...");
|
|
31841
|
-
const { context, moduleAccess } = await loadServerContext(ws.packages);
|
|
31842
|
-
if (context) {
|
|
31843
|
-
ok("Context loaded");
|
|
31844
|
-
} else {
|
|
31845
|
-
log2("No context \u2014 server endpoints skipped");
|
|
31846
|
-
}
|
|
31847
|
-
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
31848
|
-
if (deployApi)
|
|
31849
|
-
ok("Deploy API enabled (/api/deploy/*)");
|
|
31850
|
-
const platform3 = await startPlatformServer({
|
|
31851
|
-
ws,
|
|
31852
|
-
port,
|
|
31853
|
-
manifest,
|
|
31854
|
-
context,
|
|
31855
|
-
moduleAccess,
|
|
31856
|
-
dbPath: join22(ws.rootDir, ".arc", "data", "prod.db"),
|
|
31857
|
-
devMode: false,
|
|
31858
|
-
deployApi,
|
|
31859
|
-
arcEntries
|
|
31860
|
-
});
|
|
31861
|
-
ok(`Server on http://localhost:${port}`);
|
|
31862
|
-
if (platform3.contextHandler)
|
|
31863
|
-
ok("Commands, queries, WebSocket \u2014 all on same port");
|
|
31864
|
-
registerSignalCleanup(platform3);
|
|
31865
|
-
}
|
|
31866
|
-
function registerSignalCleanup(platform3) {
|
|
31867
|
-
const cleanup = () => {
|
|
31868
|
-
platform3.stop();
|
|
31869
|
-
process.exit(0);
|
|
31870
|
-
};
|
|
31871
|
-
process.on("SIGTERM", cleanup);
|
|
31872
|
-
process.on("SIGINT", cleanup);
|
|
31867
|
+
await startPlatform({ ws, devMode: false });
|
|
31873
31868
|
}
|
|
31874
31869
|
|
|
31875
31870
|
// src/index.ts
|
|
@@ -31881,8 +31876,7 @@ var platform3 = program2.command("platform").description("Platform commands \u20
|
|
|
31881
31876
|
platform3.command("dev").description("Start platform in dev mode (Bun server + Vite HMR)").option("--no-cache", "Force full rebuild on startup").action((opts) => platformDev({ noCache: opts.cache === false }));
|
|
31882
31877
|
platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
|
|
31883
31878
|
platform3.command("start").description("Start platform in production mode (requires prior build)").action(platformStart);
|
|
31884
|
-
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").action((env2, opts) => platformDeploy(env2, opts));
|
|
31885
|
-
program2.command("_build-shell", { hidden: true }).description("Build framework shell bundles from a node_modules dir").requiredOption("--out <dir>", "Output directory for shell .js bundles").requiredOption("--from <dir>", "node_modules directory to discover packages in").action((opts) => buildShell(opts));
|
|
31879
|
+
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").action((env2, opts) => platformDeploy(env2, opts));
|
|
31886
31880
|
program2.parse(process.argv);
|
|
31887
31881
|
if (process.argv.length === 2) {
|
|
31888
31882
|
program2.help();
|