@arcote.tech/arc-cli 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1696 -1663
- package/package.json +7 -7
- package/src/builder/access-extractor.ts +64 -46
- 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 +322 -106
- package/src/commands/platform-build.ts +2 -1
- package/src/commands/platform-deploy.ts +121 -64
- package/src/commands/platform-dev.ts +11 -100
- package/src/commands/platform-start.ts +4 -90
- package/src/deploy/ansible.ts +23 -3
- package/src/deploy/assets/ansible/site.yml +23 -7
- package/src/deploy/assets.ts +23 -7
- package/src/deploy/bootstrap.ts +270 -10
- package/src/deploy/caddyfile.ts +19 -23
- package/src/deploy/compose.ts +44 -27
- package/src/deploy/config.ts +67 -3
- package/src/deploy/deploy-env.ts +129 -0
- package/src/deploy/env-file.ts +103 -0
- package/src/deploy/htpasswd.ts +28 -0
- package/src/deploy/image-template.ts +74 -0
- package/src/deploy/image.ts +243 -0
- package/src/deploy/registry.ts +79 -0
- package/src/deploy/ssh.ts +52 -122
- package/src/deploy/survey.ts +64 -0
- package/src/index.ts +20 -13
- package/src/platform/server.ts +119 -94
- package/src/platform/shared.ts +139 -292
- package/src/platform/startup.ts +159 -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
|
@@ -8974,6 +8974,285 @@ var require_chokidar = __commonJS((exports) => {
|
|
|
8974
8974
|
exports.watch = watch;
|
|
8975
8975
|
});
|
|
8976
8976
|
|
|
8977
|
+
// src/i18n/catalog.ts
|
|
8978
|
+
function hashMsgid(msgid) {
|
|
8979
|
+
const hasher = new Bun.CryptoHasher("md5");
|
|
8980
|
+
hasher.update(msgid);
|
|
8981
|
+
return hasher.digest("hex").slice(0, 8);
|
|
8982
|
+
}
|
|
8983
|
+
function parsePo(content) {
|
|
8984
|
+
const entries = [];
|
|
8985
|
+
const lines = content.split(`
|
|
8986
|
+
`);
|
|
8987
|
+
let locations = [];
|
|
8988
|
+
let hash = "";
|
|
8989
|
+
let msgid = "";
|
|
8990
|
+
let msgstr = "";
|
|
8991
|
+
let obsolete = false;
|
|
8992
|
+
let inManual = false;
|
|
8993
|
+
const flush = () => {
|
|
8994
|
+
if (msgid) {
|
|
8995
|
+
entries.push({
|
|
8996
|
+
msgid,
|
|
8997
|
+
msgstr,
|
|
8998
|
+
locations,
|
|
8999
|
+
hash: hash || hashMsgid(msgid),
|
|
9000
|
+
obsolete,
|
|
9001
|
+
...inManual ? { manual: true } : {}
|
|
9002
|
+
});
|
|
9003
|
+
}
|
|
9004
|
+
locations = [];
|
|
9005
|
+
hash = "";
|
|
9006
|
+
msgid = "";
|
|
9007
|
+
msgstr = "";
|
|
9008
|
+
obsolete = false;
|
|
9009
|
+
};
|
|
9010
|
+
for (const line of lines) {
|
|
9011
|
+
const trimmed = line.trim();
|
|
9012
|
+
if (trimmed === "# manual") {
|
|
9013
|
+
inManual = true;
|
|
9014
|
+
continue;
|
|
9015
|
+
}
|
|
9016
|
+
if (trimmed === "# end manual") {
|
|
9017
|
+
if (msgid)
|
|
9018
|
+
flush();
|
|
9019
|
+
inManual = false;
|
|
9020
|
+
continue;
|
|
9021
|
+
}
|
|
9022
|
+
if (trimmed === "" || trimmed.startsWith("#,")) {
|
|
9023
|
+
if (msgid)
|
|
9024
|
+
flush();
|
|
9025
|
+
continue;
|
|
9026
|
+
}
|
|
9027
|
+
if (trimmed.startsWith("#:")) {
|
|
9028
|
+
if (msgid)
|
|
9029
|
+
flush();
|
|
9030
|
+
locations.push(trimmed.slice(3).trim());
|
|
9031
|
+
continue;
|
|
9032
|
+
}
|
|
9033
|
+
if (trimmed.startsWith("#.")) {
|
|
9034
|
+
const hashMatch = trimmed.match(/hash:(\w+)/);
|
|
9035
|
+
if (hashMatch)
|
|
9036
|
+
hash = hashMatch[1];
|
|
9037
|
+
continue;
|
|
9038
|
+
}
|
|
9039
|
+
if (trimmed.startsWith("#~")) {
|
|
9040
|
+
const rest = trimmed.slice(3).trim();
|
|
9041
|
+
if (rest.startsWith("msgid")) {
|
|
9042
|
+
if (msgid)
|
|
9043
|
+
flush();
|
|
9044
|
+
obsolete = true;
|
|
9045
|
+
msgid = extractQuoted(rest.slice(5));
|
|
9046
|
+
} else if (rest.startsWith("msgstr")) {
|
|
9047
|
+
msgstr = extractQuoted(rest.slice(6));
|
|
9048
|
+
}
|
|
9049
|
+
continue;
|
|
9050
|
+
}
|
|
9051
|
+
if (trimmed.startsWith("msgid")) {
|
|
9052
|
+
if (msgid)
|
|
9053
|
+
flush();
|
|
9054
|
+
msgid = extractQuoted(trimmed.slice(5));
|
|
9055
|
+
} else if (trimmed.startsWith("msgstr")) {
|
|
9056
|
+
msgstr = extractQuoted(trimmed.slice(6));
|
|
9057
|
+
}
|
|
9058
|
+
}
|
|
9059
|
+
flush();
|
|
9060
|
+
return entries;
|
|
9061
|
+
}
|
|
9062
|
+
function writePo(entries) {
|
|
9063
|
+
const lines = [];
|
|
9064
|
+
const manual = entries.filter((e) => e.manual);
|
|
9065
|
+
const active = entries.filter((e) => !e.obsolete && !e.manual);
|
|
9066
|
+
const obsolete = entries.filter((e) => e.obsolete);
|
|
9067
|
+
if (manual.length > 0) {
|
|
9068
|
+
lines.push("# manual");
|
|
9069
|
+
for (const entry of manual) {
|
|
9070
|
+
lines.push(`msgid ${quoteString(entry.msgid)}`);
|
|
9071
|
+
lines.push(`msgstr ${quoteString(entry.msgstr)}`);
|
|
9072
|
+
lines.push("");
|
|
9073
|
+
}
|
|
9074
|
+
lines.push("# end manual");
|
|
9075
|
+
lines.push("");
|
|
9076
|
+
}
|
|
9077
|
+
for (const entry of active) {
|
|
9078
|
+
for (const loc of entry.locations) {
|
|
9079
|
+
lines.push(`#: ${loc}`);
|
|
9080
|
+
}
|
|
9081
|
+
lines.push(`#. hash:${entry.hash}`);
|
|
9082
|
+
lines.push(`msgid ${quoteString(entry.msgid)}`);
|
|
9083
|
+
lines.push(`msgstr ${quoteString(entry.msgstr)}`);
|
|
9084
|
+
lines.push("");
|
|
9085
|
+
}
|
|
9086
|
+
if (obsolete.length > 0) {
|
|
9087
|
+
lines.push("# Obsolete entries");
|
|
9088
|
+
lines.push("");
|
|
9089
|
+
for (const entry of obsolete) {
|
|
9090
|
+
lines.push(`#~ msgid ${quoteString(entry.msgid)}`);
|
|
9091
|
+
lines.push(`#~ msgstr ${quoteString(entry.msgstr)}`);
|
|
9092
|
+
lines.push("");
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
return lines.join(`
|
|
9096
|
+
`);
|
|
9097
|
+
}
|
|
9098
|
+
function mergeCatalog(existing, extracted) {
|
|
9099
|
+
const existingMap = new Map;
|
|
9100
|
+
for (const entry of existing) {
|
|
9101
|
+
existingMap.set(entry.msgid, entry);
|
|
9102
|
+
}
|
|
9103
|
+
const manualEntries = existing.filter((e) => e.manual);
|
|
9104
|
+
const manualIds = new Set(manualEntries.map((e) => e.msgid));
|
|
9105
|
+
const result = [];
|
|
9106
|
+
const seen = new Set;
|
|
9107
|
+
for (const [msgid, locations] of extracted) {
|
|
9108
|
+
seen.add(msgid);
|
|
9109
|
+
if (manualIds.has(msgid))
|
|
9110
|
+
continue;
|
|
9111
|
+
const prev = existingMap.get(msgid);
|
|
9112
|
+
result.push({
|
|
9113
|
+
msgid,
|
|
9114
|
+
msgstr: prev?.msgstr ?? "",
|
|
9115
|
+
locations: [...locations].sort(),
|
|
9116
|
+
hash: hashMsgid(msgid),
|
|
9117
|
+
obsolete: false
|
|
9118
|
+
});
|
|
9119
|
+
}
|
|
9120
|
+
for (const entry of existing) {
|
|
9121
|
+
if (!seen.has(entry.msgid) && !entry.obsolete && !entry.manual && entry.msgstr) {
|
|
9122
|
+
result.push({
|
|
9123
|
+
...entry,
|
|
9124
|
+
obsolete: true,
|
|
9125
|
+
locations: []
|
|
9126
|
+
});
|
|
9127
|
+
}
|
|
9128
|
+
}
|
|
9129
|
+
result.sort((a, b) => a.msgid.localeCompare(b.msgid));
|
|
9130
|
+
return [...manualEntries, ...result];
|
|
9131
|
+
}
|
|
9132
|
+
function extractQuoted(s) {
|
|
9133
|
+
const trimmed = s.trim();
|
|
9134
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
9135
|
+
return trimmed.slice(1, -1).replace(/\\n/g, `
|
|
9136
|
+
`).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
9137
|
+
}
|
|
9138
|
+
return trimmed;
|
|
9139
|
+
}
|
|
9140
|
+
function quoteString(s) {
|
|
9141
|
+
const escaped = s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
9142
|
+
return `"${escaped}"`;
|
|
9143
|
+
}
|
|
9144
|
+
|
|
9145
|
+
// src/i18n/compile.ts
|
|
9146
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
9147
|
+
import { join as join4 } from "path";
|
|
9148
|
+
function compileCatalog(poPath) {
|
|
9149
|
+
const content = readFileSync3(poPath, "utf-8");
|
|
9150
|
+
const entries = parsePo(content);
|
|
9151
|
+
const result = {};
|
|
9152
|
+
for (const entry of entries) {
|
|
9153
|
+
if (!entry.obsolete && entry.msgstr) {
|
|
9154
|
+
result[entry.msgid] = entry.msgstr;
|
|
9155
|
+
}
|
|
9156
|
+
}
|
|
9157
|
+
const sorted = {};
|
|
9158
|
+
for (const key of Object.keys(result).sort()) {
|
|
9159
|
+
sorted[key] = result[key];
|
|
9160
|
+
}
|
|
9161
|
+
return sorted;
|
|
9162
|
+
}
|
|
9163
|
+
function compileAllCatalogs(localesDir, outDir) {
|
|
9164
|
+
mkdirSync3(outDir, { recursive: true });
|
|
9165
|
+
for (const file of readdirSync2(localesDir)) {
|
|
9166
|
+
if (!file.endsWith(".po"))
|
|
9167
|
+
continue;
|
|
9168
|
+
const locale = file.replace(".po", "");
|
|
9169
|
+
const compiled = compileCatalog(join4(localesDir, file));
|
|
9170
|
+
writeFileSync3(join4(outDir, `${locale}.json`), JSON.stringify(compiled));
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
var init_compile = () => {};
|
|
9174
|
+
|
|
9175
|
+
// src/i18n/plugin.ts
|
|
9176
|
+
function i18nExtractPlugin(collector, rootDir) {
|
|
9177
|
+
return {
|
|
9178
|
+
name: "arc-i18n-extract",
|
|
9179
|
+
setup(build2) {
|
|
9180
|
+
build2.onLoad({ filter: /\.tsx?$/ }, async (args) => {
|
|
9181
|
+
const source = await Bun.file(args.path).text();
|
|
9182
|
+
const relPath = args.path.startsWith(rootDir) ? args.path.slice(rootDir.length + 1) : args.path;
|
|
9183
|
+
const transRegex = /<Trans>\s*([^<{]+?)\s*<\/Trans>/g;
|
|
9184
|
+
for (const match2 of source.matchAll(transRegex)) {
|
|
9185
|
+
const msgid = match2[1].trim();
|
|
9186
|
+
if (!msgid)
|
|
9187
|
+
continue;
|
|
9188
|
+
const line = source.substring(0, match2.index).split(`
|
|
9189
|
+
`).length;
|
|
9190
|
+
const loc = `${relPath}:${line}`;
|
|
9191
|
+
if (!collector.has(msgid))
|
|
9192
|
+
collector.set(msgid, new Set);
|
|
9193
|
+
collector.get(msgid).add(loc);
|
|
9194
|
+
}
|
|
9195
|
+
const tRegex = /\bt`([^`]+)`/g;
|
|
9196
|
+
for (const match2 of source.matchAll(tRegex)) {
|
|
9197
|
+
const msgid = match2[1];
|
|
9198
|
+
if (!msgid)
|
|
9199
|
+
continue;
|
|
9200
|
+
const line = source.substring(0, match2.index).split(`
|
|
9201
|
+
`).length;
|
|
9202
|
+
const loc = `${relPath}:${line}`;
|
|
9203
|
+
if (!collector.has(msgid))
|
|
9204
|
+
collector.set(msgid, new Set);
|
|
9205
|
+
collector.get(msgid).add(loc);
|
|
9206
|
+
}
|
|
9207
|
+
return;
|
|
9208
|
+
});
|
|
9209
|
+
}
|
|
9210
|
+
};
|
|
9211
|
+
}
|
|
9212
|
+
|
|
9213
|
+
// src/i18n/index.ts
|
|
9214
|
+
var exports_i18n = {};
|
|
9215
|
+
__export(exports_i18n, {
|
|
9216
|
+
readTranslationsConfig: () => readTranslationsConfig,
|
|
9217
|
+
i18nExtractPlugin: () => i18nExtractPlugin,
|
|
9218
|
+
finalizeTranslations: () => finalizeTranslations
|
|
9219
|
+
});
|
|
9220
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
9221
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
9222
|
+
function readTranslationsConfig(rootDir) {
|
|
9223
|
+
const pkgPath = join5(rootDir, "package.json");
|
|
9224
|
+
if (!existsSync4(pkgPath))
|
|
9225
|
+
return null;
|
|
9226
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
9227
|
+
const config = pkg.arc?.translations;
|
|
9228
|
+
if (!config?.locales?.length)
|
|
9229
|
+
return null;
|
|
9230
|
+
return {
|
|
9231
|
+
locales: config.locales,
|
|
9232
|
+
sourceLocale: config.sourceLocale ?? config.locales[0]
|
|
9233
|
+
};
|
|
9234
|
+
}
|
|
9235
|
+
async function finalizeTranslations(rootDir, outDir, collector) {
|
|
9236
|
+
const config = readTranslationsConfig(rootDir);
|
|
9237
|
+
if (!config || collector.size === 0)
|
|
9238
|
+
return;
|
|
9239
|
+
const localesJsonDir = join5(outDir, "locales");
|
|
9240
|
+
mkdirSync4(localesJsonDir, { recursive: true });
|
|
9241
|
+
console.log(` Extracted ${collector.size} translatable string(s) for ${config.locales.length} locale(s)`);
|
|
9242
|
+
for (const locale of config.locales) {
|
|
9243
|
+
const poPath = join5(rootDir, "locales", `${locale}.po`);
|
|
9244
|
+
mkdirSync4(dirname4(poPath), { recursive: true });
|
|
9245
|
+
const existing = existsSync4(poPath) ? parsePo(readFileSync4(poPath, "utf-8")) : [];
|
|
9246
|
+
const merged = mergeCatalog(existing, collector);
|
|
9247
|
+
writeFileSync4(poPath, writePo(merged));
|
|
9248
|
+
const compiled = compileCatalog(poPath);
|
|
9249
|
+
writeFileSync4(join5(localesJsonDir, `${locale}.json`), JSON.stringify(compiled));
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
var init_i18n = __esm(() => {
|
|
9253
|
+
init_compile();
|
|
9254
|
+
});
|
|
9255
|
+
|
|
8977
9256
|
// ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
|
|
8978
9257
|
var require_src = __commonJS((exports, module) => {
|
|
8979
9258
|
var ESC2 = "\x1B";
|
|
@@ -18135,7 +18414,7 @@ var {
|
|
|
18135
18414
|
// ../../node_modules/.bun/find-up@7.0.0/node_modules/find-up/index.js
|
|
18136
18415
|
import path2 from "path";
|
|
18137
18416
|
|
|
18138
|
-
// ../../node_modules/.bun/
|
|
18417
|
+
// ../../node_modules/.bun/locate-path@7.2.0/node_modules/locate-path/index.js
|
|
18139
18418
|
import process2 from "process";
|
|
18140
18419
|
import path from "path";
|
|
18141
18420
|
import fs, { promises as fsPromises } from "fs";
|
|
@@ -21686,7 +21965,7 @@ var emitWarning = (msg, type, code, fn) => {
|
|
|
21686
21965
|
var AC = globalThis.AbortController;
|
|
21687
21966
|
var AS = globalThis.AbortSignal;
|
|
21688
21967
|
if (typeof AC === "undefined") {
|
|
21689
|
-
AS = class
|
|
21968
|
+
AS = class AbortSignal {
|
|
21690
21969
|
onabort;
|
|
21691
21970
|
_onabort = [];
|
|
21692
21971
|
reason;
|
|
@@ -26129,110 +26408,9 @@ ${colors2.yellow}Type declaration errors:${colors2.reset}`);
|
|
|
26129
26408
|
}
|
|
26130
26409
|
}
|
|
26131
26410
|
|
|
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
26411
|
// src/commands/dev.ts
|
|
26234
26412
|
var import_chokidar = __toESM(require_chokidar(), 1);
|
|
26235
|
-
import { dirname as dirname3, join as
|
|
26413
|
+
import { dirname as dirname3, join as join3, relative } from "path";
|
|
26236
26414
|
function getContextForFile(filePath, contexts, configDir) {
|
|
26237
26415
|
const relativePath = relative(configDir, filePath);
|
|
26238
26416
|
for (const context of contexts) {
|
|
@@ -26349,7 +26527,7 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
|
|
|
26349
26527
|
} else {
|
|
26350
26528
|
log("Initial build complete \u2713", "green");
|
|
26351
26529
|
}
|
|
26352
|
-
const watcher = import_chokidar.default.watch([
|
|
26530
|
+
const watcher = import_chokidar.default.watch([join3(configDir, "**/*.ts"), join3(configDir, "**/*.tsx")], {
|
|
26353
26531
|
ignored: [
|
|
26354
26532
|
"**/node_modules/**",
|
|
26355
26533
|
`**/${config.outDir}/**`,
|
|
@@ -26446,306 +26624,37 @@ ${colors3.yellow}Type declaration errors:${colors3.reset}`);
|
|
|
26446
26624
|
}
|
|
26447
26625
|
|
|
26448
26626
|
// src/platform/shared.ts
|
|
26449
|
-
import { copyFileSync, existsSync as existsSync10, mkdirSync as
|
|
26450
|
-
import { dirname as dirname6, join as
|
|
26627
|
+
import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
26628
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
26451
26629
|
|
|
26452
26630
|
// src/builder/module-builder.ts
|
|
26453
26631
|
import { execSync } from "child_process";
|
|
26454
26632
|
import {
|
|
26455
|
-
existsSync as
|
|
26456
|
-
mkdirSync as
|
|
26457
|
-
readFileSync as
|
|
26458
|
-
readdirSync as
|
|
26459
|
-
rmSync
|
|
26460
|
-
writeFileSync as
|
|
26633
|
+
existsSync as existsSync7,
|
|
26634
|
+
mkdirSync as mkdirSync6,
|
|
26635
|
+
readFileSync as readFileSync7,
|
|
26636
|
+
readdirSync as readdirSync4,
|
|
26637
|
+
rmSync,
|
|
26638
|
+
writeFileSync as writeFileSync6
|
|
26461
26639
|
} from "fs";
|
|
26462
|
-
import { basename as basename2, dirname as dirname5, join as
|
|
26463
|
-
|
|
26464
|
-
|
|
26465
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
26466
|
-
import { dirname as dirname4, join as join6 } from "path";
|
|
26467
|
-
|
|
26468
|
-
// src/i18n/catalog.ts
|
|
26469
|
-
function hashMsgid(msgid) {
|
|
26470
|
-
const hasher = new Bun.CryptoHasher("md5");
|
|
26471
|
-
hasher.update(msgid);
|
|
26472
|
-
return hasher.digest("hex").slice(0, 8);
|
|
26473
|
-
}
|
|
26474
|
-
function parsePo(content) {
|
|
26475
|
-
const entries = [];
|
|
26476
|
-
const lines = content.split(`
|
|
26477
|
-
`);
|
|
26478
|
-
let locations = [];
|
|
26479
|
-
let hash = "";
|
|
26480
|
-
let msgid = "";
|
|
26481
|
-
let msgstr = "";
|
|
26482
|
-
let obsolete = false;
|
|
26483
|
-
let inManual = false;
|
|
26484
|
-
const flush = () => {
|
|
26485
|
-
if (msgid) {
|
|
26486
|
-
entries.push({
|
|
26487
|
-
msgid,
|
|
26488
|
-
msgstr,
|
|
26489
|
-
locations,
|
|
26490
|
-
hash: hash || hashMsgid(msgid),
|
|
26491
|
-
obsolete,
|
|
26492
|
-
...inManual ? { manual: true } : {}
|
|
26493
|
-
});
|
|
26494
|
-
}
|
|
26495
|
-
locations = [];
|
|
26496
|
-
hash = "";
|
|
26497
|
-
msgid = "";
|
|
26498
|
-
msgstr = "";
|
|
26499
|
-
obsolete = false;
|
|
26500
|
-
};
|
|
26501
|
-
for (const line of lines) {
|
|
26502
|
-
const trimmed = line.trim();
|
|
26503
|
-
if (trimmed === "# manual") {
|
|
26504
|
-
inManual = true;
|
|
26505
|
-
continue;
|
|
26506
|
-
}
|
|
26507
|
-
if (trimmed === "# end manual") {
|
|
26508
|
-
if (msgid)
|
|
26509
|
-
flush();
|
|
26510
|
-
inManual = false;
|
|
26511
|
-
continue;
|
|
26512
|
-
}
|
|
26513
|
-
if (trimmed === "" || trimmed.startsWith("#,")) {
|
|
26514
|
-
if (msgid)
|
|
26515
|
-
flush();
|
|
26516
|
-
continue;
|
|
26517
|
-
}
|
|
26518
|
-
if (trimmed.startsWith("#:")) {
|
|
26519
|
-
if (msgid)
|
|
26520
|
-
flush();
|
|
26521
|
-
locations.push(trimmed.slice(3).trim());
|
|
26522
|
-
continue;
|
|
26523
|
-
}
|
|
26524
|
-
if (trimmed.startsWith("#.")) {
|
|
26525
|
-
const hashMatch = trimmed.match(/hash:(\w+)/);
|
|
26526
|
-
if (hashMatch)
|
|
26527
|
-
hash = hashMatch[1];
|
|
26528
|
-
continue;
|
|
26529
|
-
}
|
|
26530
|
-
if (trimmed.startsWith("#~")) {
|
|
26531
|
-
const rest = trimmed.slice(3).trim();
|
|
26532
|
-
if (rest.startsWith("msgid")) {
|
|
26533
|
-
if (msgid)
|
|
26534
|
-
flush();
|
|
26535
|
-
obsolete = true;
|
|
26536
|
-
msgid = extractQuoted(rest.slice(5));
|
|
26537
|
-
} else if (rest.startsWith("msgstr")) {
|
|
26538
|
-
msgstr = extractQuoted(rest.slice(6));
|
|
26539
|
-
}
|
|
26540
|
-
continue;
|
|
26541
|
-
}
|
|
26542
|
-
if (trimmed.startsWith("msgid")) {
|
|
26543
|
-
if (msgid)
|
|
26544
|
-
flush();
|
|
26545
|
-
msgid = extractQuoted(trimmed.slice(5));
|
|
26546
|
-
} else if (trimmed.startsWith("msgstr")) {
|
|
26547
|
-
msgstr = extractQuoted(trimmed.slice(6));
|
|
26548
|
-
}
|
|
26549
|
-
}
|
|
26550
|
-
flush();
|
|
26551
|
-
return entries;
|
|
26552
|
-
}
|
|
26553
|
-
function writePo(entries) {
|
|
26554
|
-
const lines = [];
|
|
26555
|
-
const manual = entries.filter((e) => e.manual);
|
|
26556
|
-
const active = entries.filter((e) => !e.obsolete && !e.manual);
|
|
26557
|
-
const obsolete = entries.filter((e) => e.obsolete);
|
|
26558
|
-
if (manual.length > 0) {
|
|
26559
|
-
lines.push("# manual");
|
|
26560
|
-
for (const entry of manual) {
|
|
26561
|
-
lines.push(`msgid ${quoteString(entry.msgid)}`);
|
|
26562
|
-
lines.push(`msgstr ${quoteString(entry.msgstr)}`);
|
|
26563
|
-
lines.push("");
|
|
26564
|
-
}
|
|
26565
|
-
lines.push("# end manual");
|
|
26566
|
-
lines.push("");
|
|
26567
|
-
}
|
|
26568
|
-
for (const entry of active) {
|
|
26569
|
-
for (const loc of entry.locations) {
|
|
26570
|
-
lines.push(`#: ${loc}`);
|
|
26571
|
-
}
|
|
26572
|
-
lines.push(`#. hash:${entry.hash}`);
|
|
26573
|
-
lines.push(`msgid ${quoteString(entry.msgid)}`);
|
|
26574
|
-
lines.push(`msgstr ${quoteString(entry.msgstr)}`);
|
|
26575
|
-
lines.push("");
|
|
26576
|
-
}
|
|
26577
|
-
if (obsolete.length > 0) {
|
|
26578
|
-
lines.push("# Obsolete entries");
|
|
26579
|
-
lines.push("");
|
|
26580
|
-
for (const entry of obsolete) {
|
|
26581
|
-
lines.push(`#~ msgid ${quoteString(entry.msgid)}`);
|
|
26582
|
-
lines.push(`#~ msgstr ${quoteString(entry.msgstr)}`);
|
|
26583
|
-
lines.push("");
|
|
26584
|
-
}
|
|
26585
|
-
}
|
|
26586
|
-
return lines.join(`
|
|
26587
|
-
`);
|
|
26588
|
-
}
|
|
26589
|
-
function mergeCatalog(existing, extracted) {
|
|
26590
|
-
const existingMap = new Map;
|
|
26591
|
-
for (const entry of existing) {
|
|
26592
|
-
existingMap.set(entry.msgid, entry);
|
|
26593
|
-
}
|
|
26594
|
-
const manualEntries = existing.filter((e) => e.manual);
|
|
26595
|
-
const manualIds = new Set(manualEntries.map((e) => e.msgid));
|
|
26596
|
-
const result = [];
|
|
26597
|
-
const seen = new Set;
|
|
26598
|
-
for (const [msgid, locations] of extracted) {
|
|
26599
|
-
seen.add(msgid);
|
|
26600
|
-
if (manualIds.has(msgid))
|
|
26601
|
-
continue;
|
|
26602
|
-
const prev = existingMap.get(msgid);
|
|
26603
|
-
result.push({
|
|
26604
|
-
msgid,
|
|
26605
|
-
msgstr: prev?.msgstr ?? "",
|
|
26606
|
-
locations: [...locations].sort(),
|
|
26607
|
-
hash: hashMsgid(msgid),
|
|
26608
|
-
obsolete: false
|
|
26609
|
-
});
|
|
26610
|
-
}
|
|
26611
|
-
for (const entry of existing) {
|
|
26612
|
-
if (!seen.has(entry.msgid) && !entry.obsolete && !entry.manual && entry.msgstr) {
|
|
26613
|
-
result.push({
|
|
26614
|
-
...entry,
|
|
26615
|
-
obsolete: true,
|
|
26616
|
-
locations: []
|
|
26617
|
-
});
|
|
26618
|
-
}
|
|
26619
|
-
}
|
|
26620
|
-
result.sort((a, b) => a.msgid.localeCompare(b.msgid));
|
|
26621
|
-
return [...manualEntries, ...result];
|
|
26622
|
-
}
|
|
26623
|
-
function extractQuoted(s) {
|
|
26624
|
-
const trimmed = s.trim();
|
|
26625
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
26626
|
-
return trimmed.slice(1, -1).replace(/\\n/g, `
|
|
26627
|
-
`).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
26628
|
-
}
|
|
26629
|
-
return trimmed;
|
|
26630
|
-
}
|
|
26631
|
-
function quoteString(s) {
|
|
26632
|
-
const escaped = s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
26633
|
-
return `"${escaped}"`;
|
|
26634
|
-
}
|
|
26635
|
-
|
|
26636
|
-
// src/i18n/compile.ts
|
|
26637
|
-
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
26638
|
-
import { join as join5 } from "path";
|
|
26639
|
-
function compileCatalog(poPath) {
|
|
26640
|
-
const content = readFileSync4(poPath, "utf-8");
|
|
26641
|
-
const entries = parsePo(content);
|
|
26642
|
-
const result = {};
|
|
26643
|
-
for (const entry of entries) {
|
|
26644
|
-
if (!entry.obsolete && entry.msgstr) {
|
|
26645
|
-
result[entry.msgid] = entry.msgstr;
|
|
26646
|
-
}
|
|
26647
|
-
}
|
|
26648
|
-
const sorted = {};
|
|
26649
|
-
for (const key of Object.keys(result).sort()) {
|
|
26650
|
-
sorted[key] = result[key];
|
|
26651
|
-
}
|
|
26652
|
-
return sorted;
|
|
26653
|
-
}
|
|
26654
|
-
function compileAllCatalogs(localesDir, outDir) {
|
|
26655
|
-
mkdirSync4(outDir, { recursive: true });
|
|
26656
|
-
for (const file of readdirSync3(localesDir)) {
|
|
26657
|
-
if (!file.endsWith(".po"))
|
|
26658
|
-
continue;
|
|
26659
|
-
const locale = file.replace(".po", "");
|
|
26660
|
-
const compiled = compileCatalog(join5(localesDir, file));
|
|
26661
|
-
writeFileSync4(join5(outDir, `${locale}.json`), JSON.stringify(compiled));
|
|
26662
|
-
}
|
|
26663
|
-
}
|
|
26664
|
-
|
|
26665
|
-
// src/i18n/plugin.ts
|
|
26666
|
-
function i18nExtractPlugin(collector, rootDir) {
|
|
26667
|
-
return {
|
|
26668
|
-
name: "arc-i18n-extract",
|
|
26669
|
-
setup(build2) {
|
|
26670
|
-
build2.onLoad({ filter: /\.tsx?$/ }, async (args) => {
|
|
26671
|
-
const source = await Bun.file(args.path).text();
|
|
26672
|
-
const relPath = args.path.startsWith(rootDir) ? args.path.slice(rootDir.length + 1) : args.path;
|
|
26673
|
-
const transRegex = /<Trans>\s*([^<{]+?)\s*<\/Trans>/g;
|
|
26674
|
-
for (const match2 of source.matchAll(transRegex)) {
|
|
26675
|
-
const msgid = match2[1].trim();
|
|
26676
|
-
if (!msgid)
|
|
26677
|
-
continue;
|
|
26678
|
-
const line = source.substring(0, match2.index).split(`
|
|
26679
|
-
`).length;
|
|
26680
|
-
const loc = `${relPath}:${line}`;
|
|
26681
|
-
if (!collector.has(msgid))
|
|
26682
|
-
collector.set(msgid, new Set);
|
|
26683
|
-
collector.get(msgid).add(loc);
|
|
26684
|
-
}
|
|
26685
|
-
const tRegex = /\bt`([^`]+)`/g;
|
|
26686
|
-
for (const match2 of source.matchAll(tRegex)) {
|
|
26687
|
-
const msgid = match2[1];
|
|
26688
|
-
if (!msgid)
|
|
26689
|
-
continue;
|
|
26690
|
-
const line = source.substring(0, match2.index).split(`
|
|
26691
|
-
`).length;
|
|
26692
|
-
const loc = `${relPath}:${line}`;
|
|
26693
|
-
if (!collector.has(msgid))
|
|
26694
|
-
collector.set(msgid, new Set);
|
|
26695
|
-
collector.get(msgid).add(loc);
|
|
26696
|
-
}
|
|
26697
|
-
return;
|
|
26698
|
-
});
|
|
26699
|
-
}
|
|
26700
|
-
};
|
|
26701
|
-
}
|
|
26702
|
-
|
|
26703
|
-
// src/i18n/index.ts
|
|
26704
|
-
function readTranslationsConfig(rootDir) {
|
|
26705
|
-
const pkgPath = join6(rootDir, "package.json");
|
|
26706
|
-
if (!existsSync5(pkgPath))
|
|
26707
|
-
return null;
|
|
26708
|
-
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
26709
|
-
const config = pkg.arc?.translations;
|
|
26710
|
-
if (!config?.locales?.length)
|
|
26711
|
-
return null;
|
|
26712
|
-
return {
|
|
26713
|
-
locales: config.locales,
|
|
26714
|
-
sourceLocale: config.sourceLocale ?? config.locales[0]
|
|
26715
|
-
};
|
|
26716
|
-
}
|
|
26717
|
-
async function finalizeTranslations(rootDir, outDir, collector) {
|
|
26718
|
-
const config = readTranslationsConfig(rootDir);
|
|
26719
|
-
if (!config || collector.size === 0)
|
|
26720
|
-
return;
|
|
26721
|
-
const localesJsonDir = join6(outDir, "locales");
|
|
26722
|
-
mkdirSync5(localesJsonDir, { recursive: true });
|
|
26723
|
-
console.log(` Extracted ${collector.size} translatable string(s) for ${config.locales.length} locale(s)`);
|
|
26724
|
-
for (const locale of config.locales) {
|
|
26725
|
-
const poPath = join6(rootDir, "locales", `${locale}.po`);
|
|
26726
|
-
mkdirSync5(dirname4(poPath), { recursive: true });
|
|
26727
|
-
const existing = existsSync5(poPath) ? parsePo(readFileSync5(poPath, "utf-8")) : [];
|
|
26728
|
-
const merged = mergeCatalog(existing, collector);
|
|
26729
|
-
writeFileSync5(poPath, writePo(merged));
|
|
26730
|
-
const compiled = compileCatalog(poPath);
|
|
26731
|
-
writeFileSync5(join6(localesJsonDir, `${locale}.json`), JSON.stringify(compiled));
|
|
26732
|
-
}
|
|
26733
|
-
}
|
|
26640
|
+
import { basename as basename2, dirname as dirname5, join as join8, relative as relative3 } from "path";
|
|
26641
|
+
init_i18n();
|
|
26642
|
+
init_compile();
|
|
26734
26643
|
|
|
26735
26644
|
// src/builder/build-cache.ts
|
|
26736
|
-
import { existsSync as
|
|
26737
|
-
import { join as
|
|
26738
|
-
var CACHE_VERSION =
|
|
26645
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
26646
|
+
import { join as join6 } from "path";
|
|
26647
|
+
var CACHE_VERSION = 2;
|
|
26739
26648
|
var CACHE_FILE = ".build-cache.json";
|
|
26740
26649
|
function emptyCache() {
|
|
26741
26650
|
return { version: CACHE_VERSION, units: {} };
|
|
26742
26651
|
}
|
|
26743
26652
|
function loadBuildCache(arcDir) {
|
|
26744
|
-
const path4 =
|
|
26745
|
-
if (!
|
|
26653
|
+
const path4 = join6(arcDir, CACHE_FILE);
|
|
26654
|
+
if (!existsSync5(path4))
|
|
26746
26655
|
return emptyCache();
|
|
26747
26656
|
try {
|
|
26748
|
-
const raw = JSON.parse(
|
|
26657
|
+
const raw = JSON.parse(readFileSync5(path4, "utf-8"));
|
|
26749
26658
|
if (raw?.version !== CACHE_VERSION || typeof raw.units !== "object") {
|
|
26750
26659
|
return emptyCache();
|
|
26751
26660
|
}
|
|
@@ -26755,15 +26664,15 @@ function loadBuildCache(arcDir) {
|
|
|
26755
26664
|
}
|
|
26756
26665
|
}
|
|
26757
26666
|
function saveBuildCache(arcDir, cache) {
|
|
26758
|
-
|
|
26759
|
-
|
|
26667
|
+
mkdirSync5(arcDir, { recursive: true });
|
|
26668
|
+
writeFileSync5(join6(arcDir, CACHE_FILE), JSON.stringify(cache, null, 2));
|
|
26760
26669
|
}
|
|
26761
26670
|
function isCacheHit(cache, unitId, inputHash, requiredOutputs = []) {
|
|
26762
26671
|
const entry = cache.units[unitId];
|
|
26763
26672
|
if (!entry || entry.inputHash !== inputHash)
|
|
26764
26673
|
return false;
|
|
26765
26674
|
for (const out of requiredOutputs) {
|
|
26766
|
-
if (!
|
|
26675
|
+
if (!existsSync5(out))
|
|
26767
26676
|
return false;
|
|
26768
26677
|
}
|
|
26769
26678
|
return true;
|
|
@@ -26776,9 +26685,33 @@ function updateCache(cache, unitId, inputHash, output = {}) {
|
|
|
26776
26685
|
};
|
|
26777
26686
|
}
|
|
26778
26687
|
|
|
26688
|
+
// src/builder/framework-peers.ts
|
|
26689
|
+
var CORE_PEERS = [
|
|
26690
|
+
"@arcote.tech/arc",
|
|
26691
|
+
"@arcote.tech/arc-ds",
|
|
26692
|
+
"@arcote.tech/arc-react",
|
|
26693
|
+
"@arcote.tech/platform"
|
|
26694
|
+
];
|
|
26695
|
+
var BROWSER_FRAGMENT_PEERS = [
|
|
26696
|
+
"@arcote.tech/arc-auth",
|
|
26697
|
+
"@arcote.tech/arc-workspace",
|
|
26698
|
+
"@arcote.tech/arc-utils",
|
|
26699
|
+
"@arcote.tech/arc-chat"
|
|
26700
|
+
];
|
|
26701
|
+
var ARC_PEERS = [...CORE_PEERS, ...BROWSER_FRAGMENT_PEERS];
|
|
26702
|
+
var REACT_PEERS = ["react", "react-dom"];
|
|
26703
|
+
var FRAMEWORK_PEERS = [...ARC_PEERS, ...REACT_PEERS];
|
|
26704
|
+
var SHELL_EXTERNALS = [
|
|
26705
|
+
...FRAMEWORK_PEERS,
|
|
26706
|
+
"react/jsx-runtime",
|
|
26707
|
+
"react/jsx-dev-runtime"
|
|
26708
|
+
];
|
|
26709
|
+
var FRAMEWORK_PEER_SET = new Set(FRAMEWORK_PEERS);
|
|
26710
|
+
var SHELL_EXTERNAL_SET = new Set(SHELL_EXTERNALS);
|
|
26711
|
+
|
|
26779
26712
|
// src/builder/hash.ts
|
|
26780
|
-
import { existsSync as
|
|
26781
|
-
import { join as
|
|
26713
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync } from "fs";
|
|
26714
|
+
import { join as join7, relative as relative2, sep as sep2 } from "path";
|
|
26782
26715
|
function sha256Hex(bytes) {
|
|
26783
26716
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
26784
26717
|
hasher.update(bytes);
|
|
@@ -26788,21 +26721,21 @@ function sha256OfFiles(paths) {
|
|
|
26788
26721
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
26789
26722
|
const sorted = [...paths].sort();
|
|
26790
26723
|
for (const p of sorted) {
|
|
26791
|
-
if (!
|
|
26724
|
+
if (!existsSync6(p))
|
|
26792
26725
|
continue;
|
|
26793
|
-
hasher.update(
|
|
26726
|
+
hasher.update(readFileSync6(p));
|
|
26794
26727
|
hasher.update("\x00");
|
|
26795
26728
|
}
|
|
26796
26729
|
return hasher.digest("hex");
|
|
26797
26730
|
}
|
|
26798
26731
|
function sha256OfDir(dir, filter2) {
|
|
26799
|
-
if (!
|
|
26732
|
+
if (!existsSync6(dir))
|
|
26800
26733
|
return sha256Hex("");
|
|
26801
26734
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
26802
26735
|
const entries = [];
|
|
26803
26736
|
function walk(absDir) {
|
|
26804
|
-
for (const entry of
|
|
26805
|
-
const abs =
|
|
26737
|
+
for (const entry of readdirSync3(absDir, { withFileTypes: true })) {
|
|
26738
|
+
const abs = join7(absDir, entry.name);
|
|
26806
26739
|
const rel = relative2(dir, abs).split(sep2).join("/");
|
|
26807
26740
|
if (filter2 && !filter2(rel))
|
|
26808
26741
|
continue;
|
|
@@ -26817,7 +26750,7 @@ function sha256OfDir(dir, filter2) {
|
|
|
26817
26750
|
for (const { rel, abs } of entries) {
|
|
26818
26751
|
hasher.update(rel);
|
|
26819
26752
|
hasher.update("\x00");
|
|
26820
|
-
hasher.update(
|
|
26753
|
+
hasher.update(readFileSync6(abs));
|
|
26821
26754
|
hasher.update("\x00");
|
|
26822
26755
|
}
|
|
26823
26756
|
return hasher.digest("hex");
|
|
@@ -26836,17 +26769,17 @@ function stableStringify(value) {
|
|
|
26836
26769
|
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
26837
26770
|
}
|
|
26838
26771
|
function readInstalledVersion(rootDir, pkgName) {
|
|
26839
|
-
const pkgJson =
|
|
26840
|
-
if (!
|
|
26772
|
+
const pkgJson = join7(rootDir, "node_modules", pkgName, "package.json");
|
|
26773
|
+
if (!existsSync6(pkgJson))
|
|
26841
26774
|
return null;
|
|
26842
26775
|
try {
|
|
26843
|
-
return JSON.parse(
|
|
26776
|
+
return JSON.parse(readFileSync6(pkgJson, "utf-8")).version ?? null;
|
|
26844
26777
|
} catch {
|
|
26845
26778
|
return null;
|
|
26846
26779
|
}
|
|
26847
26780
|
}
|
|
26848
26781
|
function mtimeOf(path4) {
|
|
26849
|
-
if (!
|
|
26782
|
+
if (!existsSync6(path4))
|
|
26850
26783
|
return 0;
|
|
26851
26784
|
try {
|
|
26852
26785
|
return statSync(path4).mtimeMs;
|
|
@@ -26876,58 +26809,82 @@ async function pAll(tasks, concurrency = DEFAULT_CONCURRENCY) {
|
|
|
26876
26809
|
}
|
|
26877
26810
|
|
|
26878
26811
|
// src/builder/module-builder.ts
|
|
26812
|
+
function singleReactPlugin(rootDir) {
|
|
26813
|
+
const reactPkgs = ["react", "react-dom", "react-dom/client", "react/jsx-runtime"];
|
|
26814
|
+
return {
|
|
26815
|
+
name: "single-react",
|
|
26816
|
+
setup(build2) {
|
|
26817
|
+
const resolved = new Map;
|
|
26818
|
+
for (const spec of reactPkgs) {
|
|
26819
|
+
try {
|
|
26820
|
+
resolved.set(spec, Bun.resolveSync(spec, rootDir));
|
|
26821
|
+
} catch {}
|
|
26822
|
+
}
|
|
26823
|
+
build2.onResolve({ filter: /^(react|react-dom|react-dom\/client|react\/jsx-runtime)$/ }, (args) => {
|
|
26824
|
+
const path4 = resolved.get(args.path);
|
|
26825
|
+
return path4 ? { path: path4 } : null;
|
|
26826
|
+
});
|
|
26827
|
+
}
|
|
26828
|
+
};
|
|
26829
|
+
}
|
|
26830
|
+
function jsxDevShimPlugin() {
|
|
26831
|
+
return {
|
|
26832
|
+
name: "jsx-dev-runtime-shim",
|
|
26833
|
+
setup(build2) {
|
|
26834
|
+
build2.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
|
|
26835
|
+
path: "react-jsx-dev-runtime-shim",
|
|
26836
|
+
namespace: "jsx-dev-shim"
|
|
26837
|
+
}));
|
|
26838
|
+
build2.onLoad({ filter: /.*/, namespace: "jsx-dev-shim" }, () => ({
|
|
26839
|
+
contents: `import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
26840
|
+
export const jsxDEV = jsx;
|
|
26841
|
+
export const jsxsDEV = jsxs;
|
|
26842
|
+
export { Fragment };
|
|
26843
|
+
`,
|
|
26844
|
+
loader: "ts"
|
|
26845
|
+
}));
|
|
26846
|
+
}
|
|
26847
|
+
};
|
|
26848
|
+
}
|
|
26879
26849
|
var CONTEXT_CLIENTS = [
|
|
26880
26850
|
{ name: "server", target: "bun", defines: { ONLY_SERVER: "true", ONLY_BROWSER: "false", ONLY_CLIENT: "false" } },
|
|
26881
26851
|
{ name: "browser", target: "browser", defines: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" } }
|
|
26882
26852
|
];
|
|
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
26853
|
function discoverPackages(rootDir) {
|
|
26897
|
-
const rootPkg = JSON.parse(
|
|
26854
|
+
const rootPkg = JSON.parse(readFileSync7(join8(rootDir, "package.json"), "utf-8"));
|
|
26898
26855
|
const workspaceGlobs = rootPkg.workspaces ?? [];
|
|
26899
26856
|
const results = [];
|
|
26900
26857
|
for (const glob2 of workspaceGlobs) {
|
|
26901
26858
|
const base2 = glob2.replace("/*", "");
|
|
26902
|
-
const baseDir =
|
|
26903
|
-
if (!
|
|
26859
|
+
const baseDir = join8(rootDir, base2);
|
|
26860
|
+
if (!existsSync7(baseDir))
|
|
26904
26861
|
continue;
|
|
26905
26862
|
let entries;
|
|
26906
26863
|
try {
|
|
26907
|
-
entries =
|
|
26864
|
+
entries = readdirSync4(baseDir);
|
|
26908
26865
|
} catch {
|
|
26909
26866
|
continue;
|
|
26910
26867
|
}
|
|
26911
26868
|
for (const entry of entries) {
|
|
26912
|
-
const pkgPath =
|
|
26913
|
-
if (!
|
|
26869
|
+
const pkgPath = join8(baseDir, entry, "package.json");
|
|
26870
|
+
if (!existsSync7(pkgPath))
|
|
26914
26871
|
continue;
|
|
26915
|
-
const pkg = JSON.parse(
|
|
26872
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
26916
26873
|
if (pkg.name?.startsWith("@arcote.tech/"))
|
|
26917
26874
|
continue;
|
|
26918
|
-
const pkgDir =
|
|
26875
|
+
const pkgDir = join8(baseDir, entry);
|
|
26919
26876
|
const candidates = [
|
|
26920
|
-
|
|
26921
|
-
|
|
26922
|
-
|
|
26923
|
-
|
|
26877
|
+
join8(pkgDir, "src", "index.ts"),
|
|
26878
|
+
join8(pkgDir, "src", "index.tsx"),
|
|
26879
|
+
join8(pkgDir, "index.ts"),
|
|
26880
|
+
join8(pkgDir, "index.tsx")
|
|
26924
26881
|
];
|
|
26925
|
-
const entrypoint = candidates.find((c) =>
|
|
26882
|
+
const entrypoint = candidates.find((c) => existsSync7(c)) ?? null;
|
|
26926
26883
|
if (!entrypoint)
|
|
26927
26884
|
continue;
|
|
26928
26885
|
results.push({
|
|
26929
26886
|
name: pkg.name,
|
|
26930
|
-
path:
|
|
26887
|
+
path: join8(baseDir, entry),
|
|
26931
26888
|
entrypoint,
|
|
26932
26889
|
packageJson: pkg
|
|
26933
26890
|
});
|
|
@@ -26956,7 +26913,7 @@ var sourceFilter = (rel) => {
|
|
|
26956
26913
|
return true;
|
|
26957
26914
|
};
|
|
26958
26915
|
function pkgSourceHash(pkg) {
|
|
26959
|
-
return sha256OfDir(
|
|
26916
|
+
return sha256OfDir(join8(pkg.path, "src"), sourceFilter);
|
|
26960
26917
|
}
|
|
26961
26918
|
function depVersionsHash(rootDir, pkg) {
|
|
26962
26919
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
@@ -26969,7 +26926,7 @@ function depVersionsHash(rootDir, pkg) {
|
|
|
26969
26926
|
}
|
|
26970
26927
|
async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
26971
26928
|
const unitId = `context-pkg:${pkg.name}:${client.name}`;
|
|
26972
|
-
const outDir =
|
|
26929
|
+
const outDir = join8(pkg.path, "dist", client.name);
|
|
26973
26930
|
const inputHash = sha256OfJson({
|
|
26974
26931
|
src: pkgSourceHash(pkg),
|
|
26975
26932
|
pkg: pkg.packageJson,
|
|
@@ -26978,21 +26935,22 @@ async function buildContextClient(pkg, rootDir, client, cache, noCache) {
|
|
|
26978
26935
|
target: client.target,
|
|
26979
26936
|
defines: client.defines
|
|
26980
26937
|
});
|
|
26981
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
26938
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(outDir, "main", "index.js")])) {
|
|
26982
26939
|
console.log(` \u2713 cached: ${pkg.name} (${client.name})`);
|
|
26983
26940
|
return { pkgName: pkg.name, client: client.name, declarationErrors: [], cached: true };
|
|
26984
26941
|
}
|
|
26985
26942
|
console.log(` building: ${pkg.name} (${client.name})`);
|
|
26986
26943
|
const peerDeps = Object.keys(pkg.packageJson.peerDependencies ?? {});
|
|
26987
|
-
const
|
|
26988
|
-
const externals = [...peerDeps, ...
|
|
26944
|
+
const allDeps = pkg.packageJson.dependencies ?? {};
|
|
26945
|
+
const externals = [...peerDeps, ...Object.keys(allDeps)];
|
|
26989
26946
|
const result = await Bun.build({
|
|
26990
26947
|
entrypoints: [pkg.entrypoint],
|
|
26991
|
-
outdir:
|
|
26948
|
+
outdir: join8(outDir, "main"),
|
|
26992
26949
|
target: client.target,
|
|
26993
26950
|
format: "esm",
|
|
26994
26951
|
naming: "index.[ext]",
|
|
26995
26952
|
external: externals,
|
|
26953
|
+
plugins: [jsxDevShimPlugin()],
|
|
26996
26954
|
define: client.defines
|
|
26997
26955
|
});
|
|
26998
26956
|
if (!result.success) {
|
|
@@ -27025,115 +26983,180 @@ async function buildContextPackages(rootDir, packages, cache, noCache) {
|
|
|
27025
26983
|
}
|
|
27026
26984
|
return { declarationErrors };
|
|
27027
26985
|
}
|
|
27028
|
-
async function
|
|
27029
|
-
|
|
27030
|
-
const
|
|
27031
|
-
const
|
|
27032
|
-
|
|
27033
|
-
|
|
27034
|
-
|
|
27035
|
-
|
|
26986
|
+
async function buildBrowserApp(rootDir, outDir, plan, cache, noCache, i18nCollector) {
|
|
26987
|
+
mkdirSync6(outDir, { recursive: true });
|
|
26988
|
+
const publicMembers = plan.groups.get("public") ?? [];
|
|
26989
|
+
const protectedGroups = plan.chunks.filter((c) => c !== "public").map((c) => ({ name: c, members: plan.groups.get(c) ?? [] })).filter((g) => g.members.length > 0);
|
|
26990
|
+
const unitId = "browser-app";
|
|
26991
|
+
const allMembers = [];
|
|
26992
|
+
for (const m of publicMembers) {
|
|
26993
|
+
allMembers.push({ name: m.pkg.name, group: "public", srcHash: pkgSourceHash(m.pkg) });
|
|
26994
|
+
}
|
|
26995
|
+
for (const g of protectedGroups) {
|
|
26996
|
+
for (const m of g.members) {
|
|
26997
|
+
allMembers.push({ name: m.pkg.name, group: g.name, srcHash: pkgSourceHash(m.pkg) });
|
|
26998
|
+
}
|
|
26999
|
+
}
|
|
27036
27000
|
const inputHash = sha256OfJson({
|
|
27037
|
-
|
|
27038
|
-
|
|
27001
|
+
members: allMembers,
|
|
27002
|
+
groups: [
|
|
27003
|
+
"initial",
|
|
27004
|
+
...protectedGroups.map((g) => g.name).sort()
|
|
27005
|
+
],
|
|
27039
27006
|
define: { ONLY_SERVER: "false", ONLY_BROWSER: "true", ONLY_CLIENT: "true" }
|
|
27040
27007
|
});
|
|
27041
27008
|
if (!noCache && isCacheHit(cache, unitId, inputHash)) {
|
|
27042
|
-
const
|
|
27043
|
-
|
|
27044
|
-
|
|
27045
|
-
|
|
27046
|
-
|
|
27047
|
-
|
|
27048
|
-
|
|
27049
|
-
|
|
27050
|
-
|
|
27051
|
-
|
|
27052
|
-
|
|
27053
|
-
|
|
27054
|
-
|
|
27055
|
-
|
|
27056
|
-
|
|
27057
|
-
|
|
27058
|
-
|
|
27059
|
-
|
|
27060
|
-
|
|
27061
|
-
|
|
27062
|
-
|
|
27063
|
-
|
|
27064
|
-
|
|
27065
|
-
|
|
27066
|
-
|
|
27067
|
-
|
|
27068
|
-
writeFileSync7(wrapperFile, `export * from "${pkg.name}";
|
|
27009
|
+
const cached = cache.units[unitId]?.outputHashes;
|
|
27010
|
+
if (cached?._manifest) {
|
|
27011
|
+
try {
|
|
27012
|
+
const m = JSON.parse(cached._manifest);
|
|
27013
|
+
const allFiles = [
|
|
27014
|
+
m.initial.file,
|
|
27015
|
+
...Object.values(m.groups).map((g) => g.file),
|
|
27016
|
+
...m.sharedChunks
|
|
27017
|
+
];
|
|
27018
|
+
if (allFiles.every((f) => existsSync7(join8(outDir, f)))) {
|
|
27019
|
+
console.log(` \u2713 cached: ${unitId}`);
|
|
27020
|
+
return { ...m, cached: true };
|
|
27021
|
+
}
|
|
27022
|
+
} catch {}
|
|
27023
|
+
}
|
|
27024
|
+
}
|
|
27025
|
+
console.log(` building: ${unitId} (initial: ${publicMembers.length} modules, groups: ${protectedGroups.map((g) => `${g.name}=${g.members.length}`).join(",") || "none"})`);
|
|
27026
|
+
if (existsSync7(outDir)) {
|
|
27027
|
+
for (const f of readdirSync4(outDir)) {
|
|
27028
|
+
if (f.endsWith(".js"))
|
|
27029
|
+
rmSync(join8(outDir, f), { force: true });
|
|
27030
|
+
}
|
|
27031
|
+
}
|
|
27032
|
+
const tmpDir = join8(outDir, "_entries");
|
|
27033
|
+
mkdirSync6(tmpDir, { recursive: true });
|
|
27034
|
+
const importLines = (pkgs) => pkgs.map((m) => `import "${m.pkg.name}";`).join(`
|
|
27069
27035
|
`);
|
|
27070
|
-
|
|
27071
|
-
|
|
27072
|
-
|
|
27073
|
-
|
|
27074
|
-
|
|
27075
|
-
|
|
27076
|
-
|
|
27077
|
-
|
|
27078
|
-
|
|
27079
|
-
|
|
27080
|
-
|
|
27081
|
-
|
|
27082
|
-
|
|
27036
|
+
const initialEntry = join8(tmpDir, "initial.ts");
|
|
27037
|
+
writeFileSync6(initialEntry, `${importLines(publicMembers)}
|
|
27038
|
+
export { startApp } from "@arcote.tech/platform";
|
|
27039
|
+
`);
|
|
27040
|
+
const entryPaths = [initialEntry];
|
|
27041
|
+
const groupModuleMap = new Map;
|
|
27042
|
+
for (const g of protectedGroups) {
|
|
27043
|
+
const entry = join8(tmpDir, `${g.name}.ts`);
|
|
27044
|
+
writeFileSync6(entry, `${importLines(g.members)}
|
|
27045
|
+
`);
|
|
27046
|
+
entryPaths.push(entry);
|
|
27047
|
+
groupModuleMap.set(g.name, g.members.map((m) => m.moduleName));
|
|
27048
|
+
}
|
|
27049
|
+
const allMemberPkgs = new Map;
|
|
27050
|
+
for (const m of publicMembers)
|
|
27051
|
+
allMemberPkgs.set(m.pkg.name, m.pkg);
|
|
27052
|
+
for (const g of protectedGroups)
|
|
27053
|
+
for (const m of g.members)
|
|
27054
|
+
allMemberPkgs.set(m.pkg.name, m.pkg);
|
|
27055
|
+
const patchedPkgJsons = [];
|
|
27056
|
+
for (const pkg of allMemberPkgs.values()) {
|
|
27057
|
+
const pkgJsonPath = join8(pkg.path, "package.json");
|
|
27058
|
+
if (!existsSync7(pkgJsonPath))
|
|
27059
|
+
continue;
|
|
27060
|
+
const original = readFileSync7(pkgJsonPath, "utf-8");
|
|
27061
|
+
const parsed = JSON.parse(original);
|
|
27062
|
+
if (parsed.sideEffects === true)
|
|
27063
|
+
continue;
|
|
27064
|
+
parsed.sideEffects = true;
|
|
27065
|
+
writeFileSync6(pkgJsonPath, JSON.stringify(parsed, null, 2) + `
|
|
27066
|
+
`);
|
|
27067
|
+
patchedPkgJsons.push({ path: pkgJsonPath, original });
|
|
27068
|
+
}
|
|
27069
|
+
let result;
|
|
27070
|
+
try {
|
|
27071
|
+
result = await Bun.build({
|
|
27072
|
+
entrypoints: entryPaths,
|
|
27083
27073
|
outdir: outDir,
|
|
27084
27074
|
splitting: true,
|
|
27085
27075
|
format: "esm",
|
|
27086
27076
|
target: "browser",
|
|
27087
|
-
external:
|
|
27088
|
-
plugins: [
|
|
27077
|
+
external: [],
|
|
27078
|
+
plugins: [
|
|
27079
|
+
singleReactPlugin(rootDir),
|
|
27080
|
+
jsxDevShimPlugin(),
|
|
27081
|
+
i18nExtractPlugin(i18nCollector, rootDir)
|
|
27082
|
+
],
|
|
27089
27083
|
naming: "[name].[ext]",
|
|
27090
27084
|
define: {
|
|
27091
27085
|
ONLY_SERVER: "false",
|
|
27092
27086
|
ONLY_BROWSER: "true",
|
|
27093
|
-
ONLY_CLIENT: "true"
|
|
27087
|
+
ONLY_CLIENT: "true",
|
|
27088
|
+
"process.env.NODE_ENV": '"production"'
|
|
27094
27089
|
}
|
|
27095
27090
|
});
|
|
27096
|
-
|
|
27097
|
-
|
|
27098
|
-
|
|
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 {
|
|
27112
|
-
file,
|
|
27113
|
-
name: fileToName.get(safeName) ?? safeName,
|
|
27114
|
-
hash
|
|
27115
|
-
};
|
|
27116
|
-
});
|
|
27117
|
-
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
27118
|
-
return { modules, cached: false };
|
|
27091
|
+
} finally {
|
|
27092
|
+
for (const p of patchedPkgJsons)
|
|
27093
|
+
writeFileSync6(p.path, p.original);
|
|
27119
27094
|
}
|
|
27095
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
27096
|
+
if (!result.success) {
|
|
27097
|
+
for (const log2 of result.logs)
|
|
27098
|
+
console.error(log2);
|
|
27099
|
+
throw new Error("Browser app build failed");
|
|
27100
|
+
}
|
|
27101
|
+
let initialFile = "";
|
|
27102
|
+
let initialHash = "";
|
|
27103
|
+
const groups = {};
|
|
27104
|
+
const sharedChunks = [];
|
|
27105
|
+
for (const out of result.outputs) {
|
|
27106
|
+
const name = basename2(out.path);
|
|
27107
|
+
if (out.kind === "entry-point") {
|
|
27108
|
+
const bytes = readFileSync7(out.path);
|
|
27109
|
+
const hash = sha256Hex(bytes).slice(0, 16);
|
|
27110
|
+
const stem = name.replace(/\.js$/, "");
|
|
27111
|
+
const finalName = `${stem}.${hash}.js`;
|
|
27112
|
+
const finalPath = join8(outDir, finalName);
|
|
27113
|
+
rmSync(finalPath, { force: true });
|
|
27114
|
+
writeFileSync6(finalPath, bytes);
|
|
27115
|
+
rmSync(out.path, { force: true });
|
|
27116
|
+
if (stem === "initial") {
|
|
27117
|
+
initialFile = finalName;
|
|
27118
|
+
initialHash = hash;
|
|
27119
|
+
} else {
|
|
27120
|
+
groups[stem] = {
|
|
27121
|
+
file: finalName,
|
|
27122
|
+
hash,
|
|
27123
|
+
modules: groupModuleMap.get(stem) ?? []
|
|
27124
|
+
};
|
|
27125
|
+
}
|
|
27126
|
+
} else if (out.kind === "chunk") {
|
|
27127
|
+
sharedChunks.push(name);
|
|
27128
|
+
}
|
|
27129
|
+
}
|
|
27130
|
+
if (!initialFile) {
|
|
27131
|
+
throw new Error("Browser app build: initial entry not found in outputs");
|
|
27132
|
+
}
|
|
27133
|
+
const manifest = {
|
|
27134
|
+
initial: { file: initialFile, hash: initialHash },
|
|
27135
|
+
groups,
|
|
27136
|
+
sharedChunks,
|
|
27137
|
+
cached: false
|
|
27138
|
+
};
|
|
27139
|
+
updateCache(cache, unitId, inputHash, {
|
|
27140
|
+
outputHashes: { _manifest: JSON.stringify(manifest) }
|
|
27141
|
+
});
|
|
27142
|
+
return manifest;
|
|
27120
27143
|
}
|
|
27121
27144
|
async function buildTranslations(rootDir, arcDir, cache, noCache) {
|
|
27122
|
-
const localesDir =
|
|
27123
|
-
if (!
|
|
27145
|
+
const localesDir = join8(rootDir, "locales");
|
|
27146
|
+
if (!existsSync7(localesDir))
|
|
27124
27147
|
return;
|
|
27125
27148
|
const unitId = "translations";
|
|
27126
|
-
const poFiles =
|
|
27149
|
+
const poFiles = readdirSync4(localesDir).filter((f) => f.endsWith(".po")).map((f) => join8(localesDir, f));
|
|
27127
27150
|
if (poFiles.length === 0)
|
|
27128
27151
|
return;
|
|
27129
27152
|
const inputHash = sha256OfFiles(poFiles);
|
|
27130
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [
|
|
27153
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [join8(arcDir, "locales")])) {
|
|
27131
27154
|
console.log(` \u2713 cached: translations`);
|
|
27132
27155
|
return;
|
|
27133
27156
|
}
|
|
27134
27157
|
console.log(` building: translations (${poFiles.length} catalog(s))`);
|
|
27135
|
-
compileAllCatalogs(localesDir,
|
|
27136
|
-
const jsonFiles =
|
|
27158
|
+
compileAllCatalogs(localesDir, join8(arcDir, "locales"));
|
|
27159
|
+
const jsonFiles = readdirSync4(join8(arcDir, "locales")).filter((f) => f.endsWith(".json")).map((f) => join8(arcDir, "locales", f));
|
|
27137
27160
|
const outputHash = sha256OfFiles(jsonFiles);
|
|
27138
27161
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
27139
27162
|
}
|
|
@@ -27197,10 +27220,10 @@ var TAILWIND_INPUT_TEMPLATE = (rootRel) => `@import "tailwindcss";
|
|
|
27197
27220
|
}
|
|
27198
27221
|
`;
|
|
27199
27222
|
async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache) {
|
|
27200
|
-
|
|
27201
|
-
const inputCss =
|
|
27202
|
-
const outputCss =
|
|
27203
|
-
const themeOutput =
|
|
27223
|
+
mkdirSync6(arcDir, { recursive: true });
|
|
27224
|
+
const inputCss = join8(arcDir, "_input.css");
|
|
27225
|
+
const outputCss = join8(arcDir, "styles.css");
|
|
27226
|
+
const themeOutput = join8(arcDir, "theme.css");
|
|
27204
27227
|
const rootRel = relative3(arcDir, rootDir).replace(/\\/g, "/");
|
|
27205
27228
|
const inputCssContent = TAILWIND_INPUT_TEMPLATE(rootRel);
|
|
27206
27229
|
const tsxFilter = (rel) => {
|
|
@@ -27212,15 +27235,15 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
27212
27235
|
};
|
|
27213
27236
|
const wsHashes = {};
|
|
27214
27237
|
for (const p of packages) {
|
|
27215
|
-
wsHashes[p.name] = sha256OfDir(
|
|
27238
|
+
wsHashes[p.name] = sha256OfDir(join8(p.path, "src"), tsxFilter);
|
|
27216
27239
|
}
|
|
27217
|
-
const platformSrc =
|
|
27218
|
-
const arcDsSrc =
|
|
27240
|
+
const platformSrc = join8(rootDir, "node_modules", "@arcote.tech", "platform", "src");
|
|
27241
|
+
const arcDsSrc = join8(rootDir, "node_modules", "@arcote.tech", "arc-ds", "src");
|
|
27219
27242
|
const frameworkHashes = {
|
|
27220
27243
|
platform: sha256OfDir(platformSrc, tsxFilter),
|
|
27221
27244
|
arcDs: sha256OfDir(arcDsSrc, tsxFilter)
|
|
27222
27245
|
};
|
|
27223
|
-
const themeContent = themePath &&
|
|
27246
|
+
const themeContent = themePath && existsSync7(join8(rootDir, themePath)) ? readFileSync7(join8(rootDir, themePath)) : null;
|
|
27224
27247
|
const themeHash = themeContent ? sha256Hex(themeContent) : null;
|
|
27225
27248
|
const unitId = "styles";
|
|
27226
27249
|
const inputHash = sha256OfJson({
|
|
@@ -27237,142 +27260,45 @@ async function buildStyles(rootDir, arcDir, packages, themePath, cache, noCache)
|
|
|
27237
27260
|
return;
|
|
27238
27261
|
}
|
|
27239
27262
|
console.log(` building: styles`);
|
|
27240
|
-
|
|
27263
|
+
writeFileSync6(inputCss, inputCssContent);
|
|
27241
27264
|
execSync(`bunx @tailwindcss/cli -i ${inputCss} -o ${outputCss} --minify`, {
|
|
27242
27265
|
cwd: rootDir,
|
|
27243
27266
|
stdio: "inherit"
|
|
27244
27267
|
});
|
|
27245
27268
|
if (themePath && themeContent) {
|
|
27246
|
-
|
|
27269
|
+
writeFileSync6(themeOutput, themeContent);
|
|
27247
27270
|
}
|
|
27248
27271
|
const outFiles = [outputCss];
|
|
27249
|
-
if (themePath &&
|
|
27272
|
+
if (themePath && existsSync7(themeOutput))
|
|
27250
27273
|
outFiles.push(themeOutput);
|
|
27251
27274
|
const outputHash = sha256OfFiles(outFiles);
|
|
27252
27275
|
updateCache(cache, unitId, inputHash, { outputHash });
|
|
27253
27276
|
}
|
|
27254
27277
|
|
|
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
27278
|
// src/builder/access-extractor.ts
|
|
27359
27279
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
27360
|
-
import {
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
|
|
27368
|
-
|
|
27369
|
-
const
|
|
27370
|
-
|
|
27371
|
-
|
|
27372
|
-
|
|
27280
|
+
import {
|
|
27281
|
+
existsSync as existsSync8,
|
|
27282
|
+
mkdirSync as mkdirSync7,
|
|
27283
|
+
readFileSync as readFileSync8,
|
|
27284
|
+
unlinkSync as unlinkSync2,
|
|
27285
|
+
writeFileSync as writeFileSync7
|
|
27286
|
+
} from "fs";
|
|
27287
|
+
import { join as join9 } from "path";
|
|
27288
|
+
async function extractAccessMap(rootDir, packages) {
|
|
27289
|
+
const serverBundles = packages.filter((p) => isContextPackage(p.packageJson)).map((p) => ({
|
|
27290
|
+
name: p.name,
|
|
27291
|
+
path: join9(p.path, "dist", "server", "main", "index.js")
|
|
27292
|
+
})).filter((b) => existsSync8(b.path));
|
|
27293
|
+
const workerDir = join9(rootDir, ".arc", ".tmp");
|
|
27294
|
+
mkdirSync7(workerDir, { recursive: true });
|
|
27295
|
+
const workerPath = join9(workerDir, `access-extractor-${Date.now()}.mjs`);
|
|
27296
|
+
const outPath = join9(workerDir, `access-${Date.now()}.json`);
|
|
27297
|
+
writeFileSync7(workerPath, WORKER_SOURCE);
|
|
27373
27298
|
try {
|
|
27374
27299
|
const proc2 = spawn2({
|
|
27375
27300
|
cmd: ["bun", "run", workerPath],
|
|
27301
|
+
cwd: rootDir,
|
|
27376
27302
|
env: {
|
|
27377
27303
|
...process.env,
|
|
27378
27304
|
ARC_ACCESS_BUNDLES: JSON.stringify(serverBundles),
|
|
@@ -27385,17 +27311,20 @@ async function extractAccessMap(arcDir, packages) {
|
|
|
27385
27311
|
if (exit !== 0) {
|
|
27386
27312
|
throw new Error(`access-extractor subprocess exited with ${exit}`);
|
|
27387
27313
|
}
|
|
27388
|
-
return JSON.parse(
|
|
27314
|
+
return JSON.parse(readFileSync8(outPath, "utf-8"));
|
|
27389
27315
|
} finally {
|
|
27390
27316
|
try {
|
|
27391
27317
|
unlinkSync2(workerPath);
|
|
27392
27318
|
} catch {}
|
|
27319
|
+
try {
|
|
27320
|
+
unlinkSync2(outPath);
|
|
27321
|
+
} catch {}
|
|
27393
27322
|
}
|
|
27394
27323
|
}
|
|
27395
27324
|
var WORKER_SOURCE = `
|
|
27396
|
-
import { existsSync } from "node:fs";
|
|
27397
|
-
|
|
27398
27325
|
globalThis.ONLY_SERVER = true;
|
|
27326
|
+
globalThis.ONLY_BROWSER = false;
|
|
27327
|
+
globalThis.ONLY_CLIENT = false;
|
|
27399
27328
|
|
|
27400
27329
|
const bundles = JSON.parse(process.env.ARC_ACCESS_BUNDLES || "[]");
|
|
27401
27330
|
const out = process.env.ARC_ACCESS_OUT;
|
|
@@ -27403,38 +27332,153 @@ if (!out) {
|
|
|
27403
27332
|
console.error("[access-extractor-worker] ARC_ACCESS_OUT not set");
|
|
27404
27333
|
process.exit(2);
|
|
27405
27334
|
}
|
|
27406
|
-
|
|
27407
|
-
|
|
27408
|
-
|
|
27409
|
-
|
|
27410
|
-
|
|
27411
|
-
|
|
27412
|
-
|
|
27413
|
-
|
|
27414
|
-
|
|
27335
|
+
|
|
27336
|
+
// Bare-specifier import \u2014 Bun walks up from this worker's location
|
|
27337
|
+
// (<rootDir>/.arc/.tmp/) to <rootDir>/node_modules and finds the package.
|
|
27338
|
+
// Single entry (./src/index.ts) \u2014 React imports on top level are benign
|
|
27339
|
+
// (createContext, function defs); no DOM access until actual render.
|
|
27340
|
+
const platform = await import("@arcote.tech/platform");
|
|
27341
|
+
|
|
27342
|
+
for (const { name, path } of bundles) {
|
|
27343
|
+
try {
|
|
27344
|
+
await import(path);
|
|
27345
|
+
} catch (e) {
|
|
27346
|
+
console.error("[access-extractor-worker] failed to import", name, ":", e.message);
|
|
27347
|
+
// Partial map is better than total failure.
|
|
27348
|
+
}
|
|
27349
|
+
}
|
|
27350
|
+
|
|
27351
|
+
const result = {};
|
|
27352
|
+
for (const [name, access] of platform.getAllModuleAccess()) {
|
|
27353
|
+
result[name] = {
|
|
27354
|
+
rules: (access.rules ?? []).map((r) => ({
|
|
27355
|
+
token: { name: r.token?.name ?? "" },
|
|
27356
|
+
hasCheck: typeof r.check === "function",
|
|
27357
|
+
})),
|
|
27358
|
+
};
|
|
27359
|
+
}
|
|
27360
|
+
|
|
27361
|
+
const { writeFileSync } = await import("node:fs");
|
|
27362
|
+
writeFileSync(out, JSON.stringify(result, null, 2) + "\\n");
|
|
27363
|
+
`.trim();
|
|
27364
|
+
|
|
27365
|
+
// src/builder/chunk-planner.ts
|
|
27366
|
+
import { basename as basename3 } from "path";
|
|
27367
|
+
var PUBLIC_CHUNK = "public";
|
|
27368
|
+
function planChunks(packages, accessMap) {
|
|
27369
|
+
const assignments = new Map;
|
|
27370
|
+
const groups = new Map;
|
|
27371
|
+
for (const pkg of packages) {
|
|
27372
|
+
const moduleName = moduleNameOf(pkg.name);
|
|
27373
|
+
const access = accessMap[moduleName];
|
|
27374
|
+
const chunk = resolveChunk(moduleName, access);
|
|
27375
|
+
const entry = {
|
|
27376
|
+
pkg,
|
|
27377
|
+
chunk,
|
|
27378
|
+
moduleName,
|
|
27379
|
+
safeName: basename3(pkg.path)
|
|
27380
|
+
};
|
|
27381
|
+
assignments.set(moduleName, entry);
|
|
27382
|
+
const bucket = groups.get(chunk);
|
|
27383
|
+
if (bucket) {
|
|
27384
|
+
bucket.push(entry);
|
|
27385
|
+
} else {
|
|
27386
|
+
groups.set(chunk, [entry]);
|
|
27387
|
+
}
|
|
27388
|
+
}
|
|
27389
|
+
const chunks = [...groups.keys()].sort((a, b) => {
|
|
27390
|
+
if (a === PUBLIC_CHUNK)
|
|
27391
|
+
return -1;
|
|
27392
|
+
if (b === PUBLIC_CHUNK)
|
|
27393
|
+
return 1;
|
|
27394
|
+
return a.localeCompare(b);
|
|
27395
|
+
});
|
|
27396
|
+
return { assignments, groups, chunks };
|
|
27397
|
+
}
|
|
27398
|
+
function moduleNameOf(pkgName) {
|
|
27399
|
+
return pkgName.includes("/") ? pkgName.split("/").pop() : pkgName;
|
|
27400
|
+
}
|
|
27401
|
+
function resolveChunk(moduleName, access) {
|
|
27402
|
+
if (!access || access.rules.length === 0)
|
|
27403
|
+
return PUBLIC_CHUNK;
|
|
27404
|
+
const tokenNames = new Set(access.rules.map((r) => r.token.name));
|
|
27405
|
+
if (tokenNames.size === 1) {
|
|
27406
|
+
return [...tokenNames][0];
|
|
27407
|
+
}
|
|
27408
|
+
const list = [...tokenNames].sort().join(", ");
|
|
27409
|
+
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.`);
|
|
27410
|
+
}
|
|
27411
|
+
|
|
27412
|
+
// src/builder/dependency-collector.ts
|
|
27413
|
+
import { createHash } from "crypto";
|
|
27414
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
27415
|
+
import { basename as basename4, join as join10 } from "path";
|
|
27416
|
+
function collectFrameworkDeps(arcDir, rootDir, packages, sharedDeps = []) {
|
|
27417
|
+
mkdirSync8(arcDir, { recursive: true });
|
|
27418
|
+
const versions = resolveFrameworkVersions(rootDir, packages);
|
|
27419
|
+
for (const { name, version } of sharedDeps) {
|
|
27420
|
+
versions[name] = version;
|
|
27421
|
+
}
|
|
27422
|
+
const manifest = {
|
|
27423
|
+
name: "arc-platform-framework",
|
|
27424
|
+
private: true,
|
|
27425
|
+
type: "module",
|
|
27426
|
+
dependencies: versions
|
|
27427
|
+
};
|
|
27428
|
+
const manifestPath = join10(arcDir, "package.json");
|
|
27429
|
+
writeFileSync8(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
27430
|
+
`);
|
|
27431
|
+
const hash = sha256Hex2(readFileSync9(manifestPath));
|
|
27432
|
+
writeFileSync8(join10(arcDir, ".deps-hash"), hash + `
|
|
27433
|
+
`);
|
|
27434
|
+
return { hash, manifestPath };
|
|
27435
|
+
}
|
|
27436
|
+
function sha256Hex2(bytes) {
|
|
27437
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
27438
|
+
}
|
|
27439
|
+
function resolveFrameworkVersions(rootDir, packages) {
|
|
27440
|
+
const rootPkg = JSON.parse(readFileSync9(join10(rootDir, "package.json"), "utf-8"));
|
|
27441
|
+
const rootDeps = rootPkg.dependencies ?? {};
|
|
27442
|
+
const rootDevDeps = rootPkg.devDependencies ?? {};
|
|
27443
|
+
const required = new Set(FRAMEWORK_PEERS);
|
|
27444
|
+
for (const pkg of packages) {
|
|
27445
|
+
const peers = pkg.packageJson.peerDependencies ?? {};
|
|
27446
|
+
const deps = pkg.packageJson.dependencies ?? {};
|
|
27447
|
+
for (const name of Object.keys(peers)) {
|
|
27448
|
+
if (name.startsWith("@arcote.tech/"))
|
|
27449
|
+
required.add(name);
|
|
27450
|
+
}
|
|
27451
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
27452
|
+
if (spec.startsWith("workspace:"))
|
|
27453
|
+
continue;
|
|
27454
|
+
if (name.startsWith("@arcote.tech/") || !isFrameworkExternal(name))
|
|
27455
|
+
continue;
|
|
27456
|
+
required.add(name);
|
|
27457
|
+
}
|
|
27415
27458
|
}
|
|
27416
|
-
|
|
27417
|
-
|
|
27418
|
-
|
|
27419
|
-
|
|
27420
|
-
|
|
27459
|
+
const out = {};
|
|
27460
|
+
for (const name of required) {
|
|
27461
|
+
const rootSpec = rootDeps[name] ?? rootDevDeps[name];
|
|
27462
|
+
if (rootSpec) {
|
|
27463
|
+
out[name] = rootSpec;
|
|
27464
|
+
continue;
|
|
27465
|
+
}
|
|
27466
|
+
let found;
|
|
27467
|
+
for (const pkg of packages) {
|
|
27468
|
+
const spec = (pkg.packageJson.dependencies ?? {})[name] ?? (pkg.packageJson.peerDependencies ?? {})[name];
|
|
27469
|
+
if (spec) {
|
|
27470
|
+
found = spec;
|
|
27471
|
+
break;
|
|
27472
|
+
}
|
|
27473
|
+
}
|
|
27474
|
+
out[name] = found ?? "*";
|
|
27421
27475
|
}
|
|
27476
|
+
return out;
|
|
27422
27477
|
}
|
|
27423
|
-
|
|
27424
|
-
|
|
27425
|
-
for (const [name, access] of platform.getAllModuleAccess()) {
|
|
27426
|
-
result[name] = {
|
|
27427
|
-
rules: (access.rules ?? []).map((r) => ({
|
|
27428
|
-
token: { name: r.token?.name ?? "" },
|
|
27429
|
-
hasCheck: typeof r.check === "function",
|
|
27430
|
-
})),
|
|
27431
|
-
};
|
|
27478
|
+
function isFrameworkExternal(_name) {
|
|
27479
|
+
return true;
|
|
27432
27480
|
}
|
|
27433
27481
|
|
|
27434
|
-
const { writeFileSync } = await import("node:fs");
|
|
27435
|
-
writeFileSync(out, JSON.stringify(result, null, 2) + "\\n");
|
|
27436
|
-
`.trim();
|
|
27437
|
-
|
|
27438
27482
|
// src/platform/shared.ts
|
|
27439
27483
|
var C = {
|
|
27440
27484
|
reset: "\x1B[0m",
|
|
@@ -27452,22 +27496,22 @@ function resolveWorkspace() {
|
|
|
27452
27496
|
process.exit(1);
|
|
27453
27497
|
}
|
|
27454
27498
|
const rootDir = dirname6(packageJsonPath);
|
|
27455
|
-
const rootPkg = JSON.parse(
|
|
27499
|
+
const rootPkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
|
|
27456
27500
|
const appName = rootPkg.name ?? "Arc App";
|
|
27457
|
-
const arcDir =
|
|
27501
|
+
const arcDir = join11(rootDir, ".arc", "platform");
|
|
27458
27502
|
log2("Scanning workspaces...");
|
|
27459
27503
|
const packages = discoverPackages(rootDir);
|
|
27460
|
-
|
|
27461
|
-
|
|
27462
|
-
|
|
27463
|
-
|
|
27504
|
+
if (packages.length > 0) {
|
|
27505
|
+
ok(`Found ${packages.length} package(s): ${packages.map((p) => p.name).join(", ")}`);
|
|
27506
|
+
} else {
|
|
27507
|
+
log2("No workspace packages found \u2014 assuming image runtime mode.");
|
|
27464
27508
|
}
|
|
27465
27509
|
let manifest;
|
|
27466
27510
|
for (const name of ["manifest.json", "manifest.webmanifest"]) {
|
|
27467
|
-
const manifestPath =
|
|
27511
|
+
const manifestPath = join11(rootDir, name);
|
|
27468
27512
|
if (existsSync10(manifestPath)) {
|
|
27469
27513
|
try {
|
|
27470
|
-
const data = JSON.parse(
|
|
27514
|
+
const data = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
27471
27515
|
const icons = data.icons;
|
|
27472
27516
|
manifest = {
|
|
27473
27517
|
path: manifestPath,
|
|
@@ -27486,10 +27530,9 @@ function resolveWorkspace() {
|
|
|
27486
27530
|
rootPkg,
|
|
27487
27531
|
appName,
|
|
27488
27532
|
arcDir,
|
|
27489
|
-
|
|
27490
|
-
|
|
27491
|
-
|
|
27492
|
-
publicDir: join12(rootDir, "public"),
|
|
27533
|
+
browserDir: join11(arcDir, "browser"),
|
|
27534
|
+
assetsDir: join11(arcDir, "assets"),
|
|
27535
|
+
publicDir: join11(rootDir, "public"),
|
|
27493
27536
|
packages,
|
|
27494
27537
|
manifest
|
|
27495
27538
|
};
|
|
@@ -27499,63 +27542,74 @@ async function buildAll(ws, opts = {}) {
|
|
|
27499
27542
|
const noCache = opts.noCache ?? false;
|
|
27500
27543
|
const themePath = ws.rootPkg.arc?.theme;
|
|
27501
27544
|
log2(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
27545
|
+
await buildContextPackages(ws.rootDir, ws.packages, cache, noCache);
|
|
27546
|
+
copyContextServerBundles(ws);
|
|
27547
|
+
const accessMap = await extractAccessMap(ws.rootDir, ws.packages);
|
|
27548
|
+
mkdirSync9(ws.arcDir, { recursive: true });
|
|
27549
|
+
writeFileSync9(join11(ws.arcDir, "access.json"), JSON.stringify(accessMap, null, 2) + `
|
|
27550
|
+
`);
|
|
27551
|
+
const plan = planChunks(ws.packages, accessMap);
|
|
27552
|
+
ok(`Chunks: ${plan.chunks.map((c) => `${c}(${plan.groups.get(c)?.length ?? 0})`).join(", ")}`);
|
|
27553
|
+
const i18nCollector = new Map;
|
|
27554
|
+
const [browserResult] = await Promise.all([
|
|
27555
|
+
buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector),
|
|
27506
27556
|
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
27507
27557
|
copyBrowserAssets(ws, cache, noCache),
|
|
27508
27558
|
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache)
|
|
27509
27559
|
]);
|
|
27510
|
-
|
|
27560
|
+
const { finalizeTranslations: finalizeTranslations3 } = await Promise.resolve().then(() => (init_i18n(), exports_i18n));
|
|
27561
|
+
await finalizeTranslations3(ws.rootDir, ws.arcDir, i18nCollector);
|
|
27511
27562
|
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));
|
|
27563
|
+
saveBuildCache(ws.arcDir, cache);
|
|
27564
|
+
const finalManifest = assembleManifest(ws, browserResult, cache);
|
|
27565
|
+
writeFileSync9(join11(ws.arcDir, "manifest.json"), JSON.stringify(finalManifest, null, 2));
|
|
27522
27566
|
return finalManifest;
|
|
27523
27567
|
}
|
|
27524
|
-
function assembleManifest(ws,
|
|
27525
|
-
const shellEntries = {};
|
|
27526
|
-
for (const [unitId, entry] of Object.entries(cache.units)) {
|
|
27527
|
-
if (unitId.startsWith("shell:") && entry.outputHash) {
|
|
27528
|
-
shellEntries[unitId] = entry.outputHash;
|
|
27529
|
-
}
|
|
27530
|
-
}
|
|
27531
|
-
const shellHash = sha256OfJson(shellEntries);
|
|
27568
|
+
function assembleManifest(ws, browser, cache) {
|
|
27532
27569
|
const stylesHash = cache.units["styles"]?.outputHash ?? "";
|
|
27533
27570
|
return {
|
|
27534
|
-
|
|
27535
|
-
|
|
27571
|
+
initial: browser.initial,
|
|
27572
|
+
groups: browser.groups,
|
|
27573
|
+
sharedChunks: browser.sharedChunks,
|
|
27536
27574
|
stylesHash,
|
|
27537
27575
|
buildTime: new Date().toISOString()
|
|
27538
27576
|
};
|
|
27539
27577
|
}
|
|
27578
|
+
function copyContextServerBundles(ws) {
|
|
27579
|
+
const outDir = join11(ws.arcDir, "server");
|
|
27580
|
+
mkdirSync9(outDir, { recursive: true });
|
|
27581
|
+
for (const pkg of ws.packages) {
|
|
27582
|
+
if (!isContextPackage(pkg.packageJson))
|
|
27583
|
+
continue;
|
|
27584
|
+
const src = join11(pkg.path, "dist", "server", "main", "index.js");
|
|
27585
|
+
if (!existsSync10(src)) {
|
|
27586
|
+
err(`Server bundle missing for ${pkg.name}: ${src}`);
|
|
27587
|
+
continue;
|
|
27588
|
+
}
|
|
27589
|
+
const safeName = pkg.path.split("/").pop();
|
|
27590
|
+
const dst = join11(outDir, `${safeName}.js`);
|
|
27591
|
+
copyFileSync(src, dst);
|
|
27592
|
+
}
|
|
27593
|
+
}
|
|
27540
27594
|
function resolveAssetSource(from, pkgDir, rootDir) {
|
|
27541
27595
|
if (from.startsWith("./") || from.startsWith("../")) {
|
|
27542
|
-
const resolved =
|
|
27596
|
+
const resolved = join11(pkgDir, from);
|
|
27543
27597
|
return existsSync10(resolved) ? resolved : null;
|
|
27544
27598
|
}
|
|
27545
27599
|
const candidates = [
|
|
27546
|
-
|
|
27547
|
-
|
|
27600
|
+
join11(rootDir, "node_modules", from),
|
|
27601
|
+
join11(pkgDir, "node_modules", from)
|
|
27548
27602
|
];
|
|
27549
27603
|
for (const c of candidates) {
|
|
27550
27604
|
if (existsSync10(c))
|
|
27551
27605
|
return c;
|
|
27552
27606
|
}
|
|
27553
|
-
const bunCacheDir =
|
|
27607
|
+
const bunCacheDir = join11(rootDir, "node_modules", ".bun");
|
|
27554
27608
|
if (existsSync10(bunCacheDir)) {
|
|
27555
|
-
for (const entry of
|
|
27609
|
+
for (const entry of readdirSync5(bunCacheDir, { withFileTypes: true })) {
|
|
27556
27610
|
if (!entry.isDirectory())
|
|
27557
27611
|
continue;
|
|
27558
|
-
const candidate =
|
|
27612
|
+
const candidate = join11(bunCacheDir, entry.name, "node_modules", from);
|
|
27559
27613
|
if (existsSync10(candidate))
|
|
27560
27614
|
return candidate;
|
|
27561
27615
|
}
|
|
@@ -27563,11 +27617,11 @@ function resolveAssetSource(from, pkgDir, rootDir) {
|
|
|
27563
27617
|
return null;
|
|
27564
27618
|
}
|
|
27565
27619
|
function readBrowserAssets(pkgDir) {
|
|
27566
|
-
const pkgJsonPath =
|
|
27620
|
+
const pkgJsonPath = join11(pkgDir, "package.json");
|
|
27567
27621
|
if (!existsSync10(pkgJsonPath))
|
|
27568
27622
|
return [];
|
|
27569
27623
|
try {
|
|
27570
|
-
const pkg = JSON.parse(
|
|
27624
|
+
const pkg = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
|
|
27571
27625
|
const assets = pkg.arc?.browserAssets;
|
|
27572
27626
|
if (!Array.isArray(assets))
|
|
27573
27627
|
return [];
|
|
@@ -27577,14 +27631,14 @@ function readBrowserAssets(pkgDir) {
|
|
|
27577
27631
|
}
|
|
27578
27632
|
}
|
|
27579
27633
|
function discoverBrowserAssets(ws) {
|
|
27580
|
-
const arcDir =
|
|
27634
|
+
const arcDir = join11(ws.rootDir, "node_modules", "@arcote.tech");
|
|
27581
27635
|
if (!existsSync10(arcDir))
|
|
27582
27636
|
return [];
|
|
27583
27637
|
const out = [];
|
|
27584
|
-
for (const entry of
|
|
27638
|
+
for (const entry of readdirSync5(arcDir, { withFileTypes: true })) {
|
|
27585
27639
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
27586
27640
|
continue;
|
|
27587
|
-
const pkgDir =
|
|
27641
|
+
const pkgDir = join11(arcDir, entry.name);
|
|
27588
27642
|
const assets = readBrowserAssets(pkgDir);
|
|
27589
27643
|
for (const asset of assets) {
|
|
27590
27644
|
const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
|
|
@@ -27598,7 +27652,7 @@ function discoverBrowserAssets(ws) {
|
|
|
27598
27652
|
return out;
|
|
27599
27653
|
}
|
|
27600
27654
|
async function copyBrowserAssets(ws, cache, noCache) {
|
|
27601
|
-
|
|
27655
|
+
mkdirSync9(ws.assetsDir, { recursive: true });
|
|
27602
27656
|
const assets = discoverBrowserAssets(ws);
|
|
27603
27657
|
if (assets.length === 0)
|
|
27604
27658
|
return;
|
|
@@ -27609,7 +27663,7 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
27609
27663
|
to: a.to,
|
|
27610
27664
|
mtime: mtimeOf(a.src)
|
|
27611
27665
|
})));
|
|
27612
|
-
const requiredOutputs = assets.map((a) =>
|
|
27666
|
+
const requiredOutputs = assets.map((a) => join11(ws.assetsDir, a.to));
|
|
27613
27667
|
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
27614
27668
|
console.log(` \u2713 cached: browser-assets (${assets.length})`);
|
|
27615
27669
|
return;
|
|
@@ -27617,199 +27671,48 @@ async function copyBrowserAssets(ws, cache, noCache) {
|
|
|
27617
27671
|
console.log(` building: browser-assets (${assets.length})`);
|
|
27618
27672
|
const outputHashes = {};
|
|
27619
27673
|
for (const asset of assets) {
|
|
27620
|
-
const dest =
|
|
27621
|
-
|
|
27674
|
+
const dest = join11(ws.assetsDir, asset.to);
|
|
27675
|
+
mkdirSync9(dirname6(dest), { recursive: true });
|
|
27622
27676
|
copyFileSync(asset.src, dest);
|
|
27623
|
-
outputHashes[asset.to] = sha256Hex(
|
|
27677
|
+
outputHashes[asset.to] = sha256Hex(readFileSync10(dest));
|
|
27624
27678
|
}
|
|
27625
27679
|
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
27626
27680
|
}
|
|
27627
|
-
function
|
|
27628
|
-
const seen = new Set;
|
|
27629
|
-
for (const pkg of ["@arcote.tech/arc", "@arcote.tech/arc-ds", "@arcote.tech/arc-react", "@arcote.tech/platform"]) {
|
|
27630
|
-
seen.add(pkg);
|
|
27631
|
-
}
|
|
27632
|
-
for (const wp of packages) {
|
|
27633
|
-
const peerDeps = wp.packageJson.peerDependencies ?? {};
|
|
27634
|
-
for (const dep of Object.keys(peerDeps)) {
|
|
27635
|
-
if (dep.startsWith("@arcote.tech/"))
|
|
27636
|
-
seen.add(dep);
|
|
27637
|
-
}
|
|
27638
|
-
}
|
|
27639
|
-
return [...seen].map((pkg) => {
|
|
27640
|
-
const short = pkg === "@arcote.tech/platform" ? "platform" : pkg.replace("@arcote.tech/", "");
|
|
27641
|
-
return [short, pkg];
|
|
27642
|
-
});
|
|
27643
|
-
}
|
|
27644
|
-
var REACT_ENTRIES2 = [
|
|
27645
|
-
[
|
|
27646
|
-
"react",
|
|
27647
|
-
`import React from "react";
|
|
27648
|
-
export default React;
|
|
27649
|
-
export const {
|
|
27650
|
-
Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
|
|
27651
|
-
cloneElement, createContext, createElement, createRef, forwardRef, isValidElement,
|
|
27652
|
-
lazy, memo, startTransition, use, useCallback, useContext, useDebugValue,
|
|
27653
|
-
useDeferredValue, useEffect, useId, useImperativeHandle, useInsertionEffect,
|
|
27654
|
-
useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
|
|
27655
|
-
useTransition, version, useActionState, useOptimistic,
|
|
27656
|
-
} = React;`
|
|
27657
|
-
],
|
|
27658
|
-
["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
|
|
27659
|
-
[
|
|
27660
|
-
"jsx-dev-runtime",
|
|
27661
|
-
`export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`
|
|
27662
|
-
],
|
|
27663
|
-
[
|
|
27664
|
-
"react-dom",
|
|
27665
|
-
`import ReactDOM from "react-dom";
|
|
27666
|
-
export default ReactDOM;
|
|
27667
|
-
export const { createPortal, flushSync } = ReactDOM;`
|
|
27668
|
-
],
|
|
27669
|
-
[
|
|
27670
|
-
"react-dom-client",
|
|
27671
|
-
`export { createRoot, hydrateRoot } from "react-dom/client";`
|
|
27672
|
-
]
|
|
27673
|
-
];
|
|
27674
|
-
var REACT_OUTPUT_FILES = REACT_ENTRIES2.map(([n]) => `${n}.js`);
|
|
27675
|
-
var SHELL_BASE_EXTERNAL2 = [
|
|
27676
|
-
"react",
|
|
27677
|
-
"react-dom",
|
|
27678
|
-
"react/jsx-runtime",
|
|
27679
|
-
"react/jsx-dev-runtime",
|
|
27680
|
-
"react-dom/client"
|
|
27681
|
-
];
|
|
27682
|
-
var sourceFilter2 = (rel) => {
|
|
27683
|
-
if (rel.startsWith("dist/") || rel.startsWith("dist"))
|
|
27684
|
-
return false;
|
|
27685
|
-
if (rel.includes("/node_modules/") || rel.startsWith("node_modules"))
|
|
27686
|
-
return false;
|
|
27687
|
-
if (rel.startsWith(".arc/") || rel.startsWith(".arc"))
|
|
27688
|
-
return false;
|
|
27689
|
-
return true;
|
|
27690
|
-
};
|
|
27691
|
-
function arcPkgSrcHash(rootDir, pkg) {
|
|
27692
|
-
const srcDir = join12(rootDir, "node_modules", pkg, "src");
|
|
27693
|
-
if (existsSync10(srcDir))
|
|
27694
|
-
return sha256OfDir(srcDir, sourceFilter2);
|
|
27695
|
-
return sha256OfDir(join12(rootDir, "node_modules", pkg), sourceFilter2);
|
|
27696
|
-
}
|
|
27697
|
-
async function buildShellReact(shellDir, tmpDir, rootDir, cache, noCache) {
|
|
27698
|
-
const unitId = "shell:react";
|
|
27699
|
-
const inputHash = sha256OfJson({
|
|
27700
|
-
react: readInstalledVersion(rootDir, "react"),
|
|
27701
|
-
"react-dom": readInstalledVersion(rootDir, "react-dom"),
|
|
27702
|
-
entries: REACT_ENTRIES2.map(([k, v]) => [k, v])
|
|
27703
|
-
});
|
|
27704
|
-
const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join12(shellDir, f));
|
|
27705
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
27706
|
-
console.log(` \u2713 cached: shell:react`);
|
|
27707
|
-
return;
|
|
27708
|
-
}
|
|
27709
|
-
console.log(` building: shell:react`);
|
|
27710
|
-
const reactEps = [];
|
|
27711
|
-
for (const [name, code] of REACT_ENTRIES2) {
|
|
27712
|
-
const f = join12(tmpDir, `${name}.ts`);
|
|
27713
|
-
await Bun.write(f, code);
|
|
27714
|
-
reactEps.push(f);
|
|
27715
|
-
}
|
|
27716
|
-
const r = await Bun.build({
|
|
27717
|
-
entrypoints: reactEps,
|
|
27718
|
-
outdir: shellDir,
|
|
27719
|
-
splitting: true,
|
|
27720
|
-
format: "esm",
|
|
27721
|
-
target: "browser",
|
|
27722
|
-
naming: "[name].[ext]"
|
|
27723
|
-
});
|
|
27724
|
-
if (!r.success) {
|
|
27725
|
-
for (const l of r.logs)
|
|
27726
|
-
console.error(l);
|
|
27727
|
-
throw new Error("Shell React build failed");
|
|
27728
|
-
}
|
|
27729
|
-
const outputHash = sha256OfFiles(requiredOutputs);
|
|
27730
|
-
updateCache(cache, unitId, inputHash, { outputHash });
|
|
27731
|
-
}
|
|
27732
|
-
async function buildShellArcEntry(shortName, pkg, allArcPkgs, shellDir, tmpDir, rootDir, cache, noCache) {
|
|
27733
|
-
const unitId = `shell:arc:${shortName}`;
|
|
27734
|
-
const otherExternals = allArcPkgs.filter((p) => p !== pkg);
|
|
27735
|
-
const inputHash = sha256OfJson({
|
|
27736
|
-
pkg,
|
|
27737
|
-
version: readInstalledVersion(rootDir, pkg),
|
|
27738
|
-
src: arcPkgSrcHash(rootDir, pkg),
|
|
27739
|
-
base: SHELL_BASE_EXTERNAL2,
|
|
27740
|
-
others: [...otherExternals].sort()
|
|
27741
|
-
});
|
|
27742
|
-
const outputFile = join12(shellDir, `${shortName}.js`);
|
|
27743
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
|
|
27744
|
-
console.log(` \u2713 cached: ${unitId}`);
|
|
27745
|
-
return;
|
|
27746
|
-
}
|
|
27747
|
-
console.log(` building: ${unitId}`);
|
|
27748
|
-
const f = join12(tmpDir, `${shortName}.ts`);
|
|
27749
|
-
await Bun.write(f, `export * from "${pkg}";
|
|
27750
|
-
`);
|
|
27751
|
-
const r = await Bun.build({
|
|
27752
|
-
entrypoints: [f],
|
|
27753
|
-
outdir: shellDir,
|
|
27754
|
-
format: "esm",
|
|
27755
|
-
target: "browser",
|
|
27756
|
-
naming: "[name].[ext]",
|
|
27757
|
-
external: [...SHELL_BASE_EXTERNAL2, ...otherExternals],
|
|
27758
|
-
define: {
|
|
27759
|
-
ONLY_SERVER: "false",
|
|
27760
|
-
ONLY_BROWSER: "true",
|
|
27761
|
-
ONLY_CLIENT: "true"
|
|
27762
|
-
}
|
|
27763
|
-
});
|
|
27764
|
-
if (!r.success) {
|
|
27765
|
-
for (const l of r.logs)
|
|
27766
|
-
console.error(l);
|
|
27767
|
-
throw new Error(`Shell build failed for ${pkg}`);
|
|
27768
|
-
}
|
|
27769
|
-
const outputHash = sha256OfFiles([outputFile]);
|
|
27770
|
-
updateCache(cache, unitId, inputHash, { outputHash });
|
|
27771
|
-
}
|
|
27772
|
-
async function buildShell2(ws, cache, noCache) {
|
|
27773
|
-
mkdirSync10(ws.shellDir, { recursive: true });
|
|
27774
|
-
const tmpDir = join12(ws.shellDir, "_tmp");
|
|
27775
|
-
mkdirSync10(tmpDir, { recursive: true });
|
|
27776
|
-
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
27777
|
-
const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
|
|
27778
|
-
const tasks = [
|
|
27779
|
-
() => buildShellReact(ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
|
|
27780
|
-
...arcEntries.map(([short, pkg]) => () => buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache))
|
|
27781
|
-
];
|
|
27782
|
-
await pAll(tasks);
|
|
27783
|
-
rmSync3(tmpDir, { recursive: true, force: true });
|
|
27784
|
-
}
|
|
27785
|
-
async function loadServerContext(packages) {
|
|
27786
|
-
const ctxPackages = packages.filter((p) => isContextPackage(p.packageJson));
|
|
27787
|
-
if (ctxPackages.length === 0)
|
|
27788
|
-
return { context: null, moduleAccess: new Map };
|
|
27681
|
+
async function loadServerContext(ws) {
|
|
27789
27682
|
globalThis.ONLY_SERVER = true;
|
|
27790
27683
|
globalThis.ONLY_BROWSER = false;
|
|
27791
27684
|
globalThis.ONLY_CLIENT = false;
|
|
27792
|
-
const platformDir =
|
|
27793
|
-
const platformPkg = JSON.parse(
|
|
27794
|
-
const platformEntry =
|
|
27685
|
+
const platformDir = join11(process.cwd(), "node_modules", "@arcote.tech", "platform");
|
|
27686
|
+
const platformPkg = JSON.parse(readFileSync10(join11(platformDir, "package.json"), "utf-8"));
|
|
27687
|
+
const platformEntry = join11(platformDir, platformPkg.main ?? "src/index.ts");
|
|
27795
27688
|
await import(platformEntry);
|
|
27796
|
-
|
|
27797
|
-
|
|
27798
|
-
|
|
27799
|
-
|
|
27800
|
-
|
|
27689
|
+
const serverDir = join11(ws.arcDir, "server");
|
|
27690
|
+
const bundles = existsSync10(serverDir) ? readdirSync5(serverDir).filter((f) => f.endsWith(".js")) : [];
|
|
27691
|
+
if (bundles.length > 0) {
|
|
27692
|
+
for (const file of bundles) {
|
|
27693
|
+
const bundlePath = join11(serverDir, file);
|
|
27694
|
+
try {
|
|
27695
|
+
await import(bundlePath);
|
|
27696
|
+
} catch (e) {
|
|
27697
|
+
err(`Failed to load server bundle ${file}: ${e}`);
|
|
27698
|
+
}
|
|
27801
27699
|
}
|
|
27802
|
-
|
|
27803
|
-
|
|
27804
|
-
|
|
27805
|
-
|
|
27700
|
+
} else if (ws.packages.length > 0) {
|
|
27701
|
+
const ctxPackages = ws.packages.filter((p) => isContextPackage(p.packageJson));
|
|
27702
|
+
for (const ctx of ctxPackages) {
|
|
27703
|
+
const serverDist = join11(ctx.path, "dist", "server", "main", "index.js");
|
|
27704
|
+
if (!existsSync10(serverDist)) {
|
|
27705
|
+
err(`Context server dist not found: ${serverDist}`);
|
|
27706
|
+
continue;
|
|
27707
|
+
}
|
|
27708
|
+
try {
|
|
27709
|
+
await import(serverDist);
|
|
27710
|
+
} catch (e) {
|
|
27711
|
+
err(`Failed to load server context from ${ctx.name}: ${e}`);
|
|
27712
|
+
}
|
|
27806
27713
|
}
|
|
27807
|
-
}
|
|
27808
|
-
|
|
27809
|
-
for (const pkg of nonCtxPackages) {
|
|
27810
|
-
try {
|
|
27811
|
-
await import(pkg.entrypoint);
|
|
27812
|
-
} catch {}
|
|
27714
|
+
} else {
|
|
27715
|
+
return { context: null, moduleAccess: new Map };
|
|
27813
27716
|
}
|
|
27814
27717
|
const { getContext, getAllModuleAccess } = await import(platformEntry);
|
|
27815
27718
|
return {
|
|
@@ -27822,23 +27725,26 @@ async function loadServerContext(packages) {
|
|
|
27822
27725
|
async function platformBuild(opts = {}) {
|
|
27823
27726
|
const ws = resolveWorkspace();
|
|
27824
27727
|
const manifest = await buildAll(ws, { noCache: opts.noCache });
|
|
27825
|
-
|
|
27728
|
+
const groupCount = Object.keys(manifest.groups).length;
|
|
27729
|
+
ok(`Platform built \u2014 initial + ${groupCount} group(s)`);
|
|
27826
27730
|
}
|
|
27827
27731
|
|
|
27828
27732
|
// src/commands/platform-deploy.ts
|
|
27829
|
-
import { existsSync as
|
|
27830
|
-
import { dirname as
|
|
27733
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
27734
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
27735
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
27831
27736
|
|
|
27832
27737
|
// src/deploy/bootstrap.ts
|
|
27833
|
-
|
|
27834
|
-
import {
|
|
27835
|
-
import {
|
|
27738
|
+
var {spawn: spawn4 } = globalThis.Bun;
|
|
27739
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
|
|
27740
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
27741
|
+
import { join as join17 } from "path";
|
|
27836
27742
|
|
|
27837
27743
|
// src/deploy/ansible.ts
|
|
27838
27744
|
import { spawn as nodeSpawn } from "child_process";
|
|
27839
|
-
import { mkdirSync as
|
|
27840
|
-
import { tmpdir
|
|
27841
|
-
import { join as
|
|
27745
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
27746
|
+
import { homedir, tmpdir } from "os";
|
|
27747
|
+
import { join as join12 } from "path";
|
|
27842
27748
|
|
|
27843
27749
|
// src/deploy/assets.ts
|
|
27844
27750
|
var TERRAFORM_MAIN_TF = `terraform {
|
|
@@ -28032,7 +27938,22 @@ var ANSIBLE_SITE_YML = `---
|
|
|
28032
27938
|
- { policy: deny, dir: incoming }
|
|
28033
27939
|
- { policy: allow, dir: outgoing }
|
|
28034
27940
|
|
|
28035
|
-
- name:
|
|
27941
|
+
- name: Remove legacy ufw limit rule on SSH (replaced by plain allow)
|
|
27942
|
+
# If a prior bootstrap installed \`ufw limit 22/tcp\`, drop it \u2014 otherwise
|
|
27943
|
+
# the limit rule shadows the allow rule and rate-throttles deploy flows.
|
|
27944
|
+
ufw:
|
|
27945
|
+
rule: limit
|
|
27946
|
+
port: "{{ ssh_port }}"
|
|
27947
|
+
proto: tcp
|
|
27948
|
+
delete: true
|
|
27949
|
+
ignore_errors: true
|
|
27950
|
+
|
|
27951
|
+
- name: Open firewall ports (SSH key-only auth, no brute-force surface)
|
|
27952
|
+
# SSH on port 22: PasswordAuthentication=no + key-only means brute force
|
|
27953
|
+
# is impossible without the operator's private key. Rate-limiting (ufw
|
|
27954
|
+
# limit / fail2ban sshd jail) breaks legitimate deploy flows that open
|
|
27955
|
+
# many short SSH connections in sequence (canSsh -> sshExec -> scp -> ...).
|
|
27956
|
+
# 80/443: Caddy ACME + app traffic, never rate-limited.
|
|
28036
27957
|
ufw:
|
|
28037
27958
|
rule: allow
|
|
28038
27959
|
port: "{{ item }}"
|
|
@@ -28046,17 +27967,18 @@ var ANSIBLE_SITE_YML = `---
|
|
|
28046
27967
|
ufw:
|
|
28047
27968
|
state: enabled
|
|
28048
27969
|
|
|
28049
|
-
- name:
|
|
27970
|
+
- name: Disable fail2ban sshd jail
|
|
27971
|
+
# Key-only SSH + ufw rate-limit make fail2ban for sshd redundant and
|
|
27972
|
+
# actively harmful when the operator's IP roams. Keep fail2ban installed
|
|
27973
|
+
# for future jails (web/db) but turn off the sshd jail explicitly.
|
|
28050
27974
|
copy:
|
|
28051
27975
|
dest: /etc/fail2ban/jail.local
|
|
28052
27976
|
content: |
|
|
28053
27977
|
[sshd]
|
|
28054
|
-
enabled =
|
|
28055
|
-
|
|
28056
|
-
maxretry = 5
|
|
28057
|
-
findtime = 600
|
|
28058
|
-
bantime = 3600
|
|
27978
|
+
enabled = false
|
|
27979
|
+
{% if extra_allowed_ips %}
|
|
28059
27980
|
ignoreip = 127.0.0.1/8 ::1 {{ extra_allowed_ips | join(' ') }}
|
|
27981
|
+
{% endif %}
|
|
28060
27982
|
mode: "0644"
|
|
28061
27983
|
notify: restart fail2ban
|
|
28062
27984
|
|
|
@@ -28095,32 +28017,46 @@ var ASSETS = {
|
|
|
28095
28017
|
}
|
|
28096
28018
|
};
|
|
28097
28019
|
async function materializeAssets(targetDir, files) {
|
|
28098
|
-
const { mkdirSync:
|
|
28099
|
-
const { join:
|
|
28100
|
-
|
|
28020
|
+
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync10 } = await import("fs");
|
|
28021
|
+
const { join: join12 } = await import("path");
|
|
28022
|
+
mkdirSync10(targetDir, { recursive: true });
|
|
28101
28023
|
for (const [name, content] of Object.entries(files)) {
|
|
28102
|
-
|
|
28024
|
+
writeFileSync10(join12(targetDir, name), content);
|
|
28103
28025
|
}
|
|
28104
28026
|
}
|
|
28105
28027
|
|
|
28106
28028
|
// src/deploy/ansible.ts
|
|
28029
|
+
function pickSshKeyForAnsible(configured) {
|
|
28030
|
+
if (configured) {
|
|
28031
|
+
const expanded = configured.startsWith("~") ? join12(homedir(), configured.slice(1)) : configured;
|
|
28032
|
+
return existsSync11(expanded) ? expanded : null;
|
|
28033
|
+
}
|
|
28034
|
+
for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
|
|
28035
|
+
const path4 = join12(homedir(), ".ssh", name);
|
|
28036
|
+
if (existsSync11(path4))
|
|
28037
|
+
return path4;
|
|
28038
|
+
}
|
|
28039
|
+
return null;
|
|
28040
|
+
}
|
|
28107
28041
|
async function runAnsible(inputs) {
|
|
28108
|
-
const workDir =
|
|
28109
|
-
|
|
28042
|
+
const workDir = join12(tmpdir(), "arc-deploy", `ansible-${Date.now()}`);
|
|
28043
|
+
mkdirSync10(workDir, { recursive: true });
|
|
28110
28044
|
await materializeAssets(workDir, ASSETS.ansible);
|
|
28111
28045
|
const user = inputs.asRoot ? "root" : inputs.target.user;
|
|
28112
28046
|
const port = inputs.ansible?.sshPort ?? inputs.target.port;
|
|
28047
|
+
const sshKey = pickSshKeyForAnsible(inputs.target.sshKey);
|
|
28048
|
+
const sshKeyArg = sshKey ? ` -o IdentitiesOnly=yes -i ${sshKey}` : "";
|
|
28113
28049
|
const inventory = [
|
|
28114
28050
|
"[arc]",
|
|
28115
28051
|
`${inputs.target.host} ansible_user=${user} ansible_port=${port}`,
|
|
28116
28052
|
"",
|
|
28117
28053
|
"[arc:vars]",
|
|
28118
|
-
|
|
28054
|
+
`ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new -o BatchMode=yes -o PreferredAuthentications=publickey${sshKeyArg}'`,
|
|
28119
28055
|
"ansible_python_interpreter=/usr/bin/python3",
|
|
28120
28056
|
""
|
|
28121
28057
|
].join(`
|
|
28122
28058
|
`);
|
|
28123
|
-
|
|
28059
|
+
writeFileSync10(join12(workDir, "inventory.ini"), inventory);
|
|
28124
28060
|
const extraVarsJson = JSON.stringify({
|
|
28125
28061
|
username: inputs.target.user,
|
|
28126
28062
|
ssh_port: port,
|
|
@@ -28159,22 +28095,17 @@ function generateCaddyfile(cfg) {
|
|
|
28159
28095
|
lines.push("");
|
|
28160
28096
|
for (const [name, env2] of Object.entries(cfg.envs)) {
|
|
28161
28097
|
lines.push(`${env2.domain} {${tlsDirective}`);
|
|
28162
|
-
lines.push(" @deploy path /api/deploy /api/deploy/*");
|
|
28163
|
-
lines.push(" respond @deploy 404");
|
|
28164
|
-
lines.push("");
|
|
28165
28098
|
lines.push(` reverse_proxy arc-${name}:5005`);
|
|
28166
28099
|
lines.push("}");
|
|
28167
28100
|
lines.push("");
|
|
28168
28101
|
}
|
|
28169
|
-
lines.push(
|
|
28170
|
-
lines.push("
|
|
28171
|
-
lines.push("
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
}
|
|
28177
|
-
lines.push(" respond 404");
|
|
28102
|
+
lines.push(`${cfg.registry.domain} {${tlsDirective}`);
|
|
28103
|
+
lines.push(" reverse_proxy registry:5000 {");
|
|
28104
|
+
lines.push(" header_up Host {host}");
|
|
28105
|
+
lines.push(" }");
|
|
28106
|
+
lines.push(" request_body {");
|
|
28107
|
+
lines.push(" max_size 5GB");
|
|
28108
|
+
lines.push(" }");
|
|
28178
28109
|
lines.push("}");
|
|
28179
28110
|
return lines.join(`
|
|
28180
28111
|
`) + `
|
|
@@ -28182,8 +28113,7 @@ function generateCaddyfile(cfg) {
|
|
|
28182
28113
|
}
|
|
28183
28114
|
|
|
28184
28115
|
// src/deploy/compose.ts
|
|
28185
|
-
|
|
28186
|
-
function generateCompose({ cfg, cliVersion }) {
|
|
28116
|
+
function generateCompose({ cfg }) {
|
|
28187
28117
|
const lines = [];
|
|
28188
28118
|
lines.push("# Generated by `arc platform deploy` \u2014 do not edit by hand.");
|
|
28189
28119
|
lines.push("");
|
|
@@ -28194,7 +28124,6 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28194
28124
|
lines.push(" ports:");
|
|
28195
28125
|
lines.push(' - "80:80"');
|
|
28196
28126
|
lines.push(' - "443:443"');
|
|
28197
|
-
lines.push(' - "127.0.0.1:2019:2019"');
|
|
28198
28127
|
lines.push(" volumes:");
|
|
28199
28128
|
lines.push(" - ./Caddyfile:/etc/caddy/Caddyfile:ro");
|
|
28200
28129
|
lines.push(" - caddy_data:/data");
|
|
@@ -28202,25 +28131,39 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28202
28131
|
lines.push(" networks:");
|
|
28203
28132
|
lines.push(" - arc-net");
|
|
28204
28133
|
lines.push("");
|
|
28134
|
+
lines.push(" registry:");
|
|
28135
|
+
lines.push(" image: registry:2");
|
|
28136
|
+
lines.push(" restart: unless-stopped");
|
|
28137
|
+
lines.push(" volumes:");
|
|
28138
|
+
lines.push(" - registry_data:/var/lib/registry");
|
|
28139
|
+
lines.push(" - ./registry-auth/htpasswd:/auth/htpasswd:ro");
|
|
28140
|
+
lines.push(" environment:");
|
|
28141
|
+
lines.push(" REGISTRY_AUTH: htpasswd");
|
|
28142
|
+
lines.push(' REGISTRY_AUTH_HTPASSWD_REALM: "Arc Registry"');
|
|
28143
|
+
lines.push(" REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd");
|
|
28144
|
+
lines.push(' REGISTRY_HTTP_HOST: "https://' + cfg.registry.domain + '"');
|
|
28145
|
+
lines.push(" networks:");
|
|
28146
|
+
lines.push(" - arc-net");
|
|
28147
|
+
lines.push(" expose:");
|
|
28148
|
+
lines.push(' - "5000"');
|
|
28149
|
+
lines.push("");
|
|
28205
28150
|
for (const [name, env2] of Object.entries(cfg.envs)) {
|
|
28151
|
+
const upperName = name.toUpperCase().replace(/-/g, "_");
|
|
28206
28152
|
lines.push(` arc-${name}:`);
|
|
28207
|
-
lines.push(
|
|
28153
|
+
lines.push(` image: \${ARC_IMAGE_${upperName}:-arc-${name}:not-deployed}`);
|
|
28154
|
+
lines.push(` container_name: arc-${name}`);
|
|
28208
28155
|
lines.push(" restart: unless-stopped");
|
|
28209
28156
|
lines.push(" volumes:");
|
|
28210
|
-
lines.push(` - arc-platform-${name}:/app/.arc/platform`);
|
|
28211
28157
|
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
28158
|
lines.push(" environment:");
|
|
28215
28159
|
lines.push(" PORT: 5005");
|
|
28216
|
-
lines.push(' ARC_DEPLOY_API: "1"');
|
|
28217
|
-
lines.push(` ARC_CLI_VERSION: ${JSON.stringify(cliVersion)}`);
|
|
28218
28160
|
const userEnv = env2.envVars ?? {};
|
|
28219
28161
|
if (!("NODE_ENV" in userEnv)) {
|
|
28220
28162
|
lines.push(" NODE_ENV: production");
|
|
28221
28163
|
}
|
|
28164
|
+
const reserved = new Set(["PORT"]);
|
|
28222
28165
|
for (const [k, v] of Object.entries(userEnv)) {
|
|
28223
|
-
if (
|
|
28166
|
+
if (reserved.has(k))
|
|
28224
28167
|
continue;
|
|
28225
28168
|
lines.push(` ${k}: ${JSON.stringify(v)}`);
|
|
28226
28169
|
}
|
|
@@ -28236,10 +28179,8 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28236
28179
|
lines.push("volumes:");
|
|
28237
28180
|
lines.push(" caddy_data:");
|
|
28238
28181
|
lines.push(" caddy_config:");
|
|
28239
|
-
lines.push("
|
|
28240
|
-
lines.push(" arc-bun-cache:");
|
|
28182
|
+
lines.push(" registry_data:");
|
|
28241
28183
|
for (const [name] of Object.entries(cfg.envs)) {
|
|
28242
|
-
lines.push(` arc-platform-${name}:`);
|
|
28243
28184
|
lines.push(` arc-data-${name}:`);
|
|
28244
28185
|
}
|
|
28245
28186
|
return lines.join(`
|
|
@@ -28247,19 +28188,32 @@ function generateCompose({ cfg, cliVersion }) {
|
|
|
28247
28188
|
`;
|
|
28248
28189
|
}
|
|
28249
28190
|
|
|
28191
|
+
// src/deploy/htpasswd.ts
|
|
28192
|
+
async function generateHtpasswd(user, password) {
|
|
28193
|
+
if (!user || !password) {
|
|
28194
|
+
throw new Error("htpasswd: user and password must both be non-empty");
|
|
28195
|
+
}
|
|
28196
|
+
const hash = await Bun.password.hash(password, {
|
|
28197
|
+
algorithm: "bcrypt",
|
|
28198
|
+
cost: 10
|
|
28199
|
+
});
|
|
28200
|
+
return `${user}:${hash}
|
|
28201
|
+
`;
|
|
28202
|
+
}
|
|
28203
|
+
|
|
28250
28204
|
// src/deploy/terraform.ts
|
|
28251
28205
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
28252
28206
|
import { createHash as createHash2 } from "crypto";
|
|
28253
|
-
import { existsSync as
|
|
28254
|
-
import { homedir } from "os";
|
|
28255
|
-
import { join as
|
|
28207
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
28208
|
+
import { homedir as homedir2 } from "os";
|
|
28209
|
+
import { join as join13 } from "path";
|
|
28256
28210
|
async function runTerraform(inputs) {
|
|
28257
28211
|
const wsHash = createHash2("sha256").update(inputs.workspaceDir).digest("hex").slice(0, 16);
|
|
28258
|
-
const workDir =
|
|
28259
|
-
|
|
28212
|
+
const workDir = join13(homedir2(), ".arc-deploy", wsHash, "tf");
|
|
28213
|
+
mkdirSync11(workDir, { recursive: true });
|
|
28260
28214
|
await materializeAssets(workDir, ASSETS.terraform);
|
|
28261
28215
|
const sshPubKey = inputs.tf.sshPublicKey ?? expandHome("~/.ssh/id_ed25519.pub");
|
|
28262
|
-
if (!
|
|
28216
|
+
if (!existsSync12(expandHome(sshPubKey))) {
|
|
28263
28217
|
throw new Error(`SSH public key not found at ${sshPubKey}. Set provision.terraform.sshPublicKey in deploy.arc.json.`);
|
|
28264
28218
|
}
|
|
28265
28219
|
const tfvars = [
|
|
@@ -28272,7 +28226,7 @@ async function runTerraform(inputs) {
|
|
|
28272
28226
|
].join(`
|
|
28273
28227
|
`) + `
|
|
28274
28228
|
`;
|
|
28275
|
-
|
|
28229
|
+
writeFileSync11(join13(workDir, "terraform.tfvars"), tfvars);
|
|
28276
28230
|
await runTf(workDir, ["init", "-input=false", "-no-color"]);
|
|
28277
28231
|
await runTf(workDir, [
|
|
28278
28232
|
"apply",
|
|
@@ -28330,18 +28284,67 @@ function expandHome(p) {
|
|
|
28330
28284
|
}
|
|
28331
28285
|
|
|
28332
28286
|
// src/deploy/config.ts
|
|
28333
|
-
import { existsSync as
|
|
28287
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
28334
28288
|
import { join as join15 } from "path";
|
|
28289
|
+
|
|
28290
|
+
// src/deploy/env-file.ts
|
|
28291
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
28292
|
+
import { join as join14 } from "path";
|
|
28293
|
+
function loadDeployEnvFiles(rootDir, envNames) {
|
|
28294
|
+
const globalsPath = join14(rootDir, "deploy.arc.env");
|
|
28295
|
+
const globals = existsSync13(globalsPath) ? parseEnvFile(readFileSync11(globalsPath, "utf-8"), globalsPath) : {};
|
|
28296
|
+
const perEnv = {};
|
|
28297
|
+
for (const name of envNames) {
|
|
28298
|
+
const envPath = join14(rootDir, `deploy.arc.${name}.env`);
|
|
28299
|
+
if (existsSync13(envPath)) {
|
|
28300
|
+
perEnv[name] = parseEnvFile(readFileSync11(envPath, "utf-8"), envPath);
|
|
28301
|
+
}
|
|
28302
|
+
}
|
|
28303
|
+
return { globals, perEnv };
|
|
28304
|
+
}
|
|
28305
|
+
function applyDeployGlobals(globals) {
|
|
28306
|
+
for (const [k, v] of Object.entries(globals)) {
|
|
28307
|
+
if (process.env[k] === undefined) {
|
|
28308
|
+
process.env[k] = v;
|
|
28309
|
+
}
|
|
28310
|
+
}
|
|
28311
|
+
}
|
|
28312
|
+
function parseEnvFile(content, pathForErrors) {
|
|
28313
|
+
const out = {};
|
|
28314
|
+
const lines = content.split(/\r?\n/);
|
|
28315
|
+
for (let i = 0;i < lines.length; i++) {
|
|
28316
|
+
const raw = lines[i];
|
|
28317
|
+
const line = raw.trim();
|
|
28318
|
+
if (!line || line.startsWith("#"))
|
|
28319
|
+
continue;
|
|
28320
|
+
const eq = line.indexOf("=");
|
|
28321
|
+
if (eq <= 0) {
|
|
28322
|
+
throw new Error(`${pathForErrors}:${i + 1}: malformed line (expected KEY=VALUE): ${raw}`);
|
|
28323
|
+
}
|
|
28324
|
+
const key = line.slice(0, eq).trim();
|
|
28325
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
28326
|
+
throw new Error(`${pathForErrors}:${i + 1}: invalid variable name "${key}"`);
|
|
28327
|
+
}
|
|
28328
|
+
let value = line.slice(eq + 1).trim();
|
|
28329
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
28330
|
+
value = value.slice(1, -1);
|
|
28331
|
+
}
|
|
28332
|
+
out[key] = value;
|
|
28333
|
+
}
|
|
28334
|
+
return out;
|
|
28335
|
+
}
|
|
28336
|
+
|
|
28337
|
+
// src/deploy/config.ts
|
|
28335
28338
|
var DEPLOY_CONFIG_FILE = "deploy.arc.json";
|
|
28336
28339
|
function deployConfigPath(rootDir) {
|
|
28337
28340
|
return join15(rootDir, DEPLOY_CONFIG_FILE);
|
|
28338
28341
|
}
|
|
28339
28342
|
function deployConfigExists(rootDir) {
|
|
28340
|
-
return
|
|
28343
|
+
return existsSync14(deployConfigPath(rootDir));
|
|
28341
28344
|
}
|
|
28342
28345
|
function loadDeployConfig(rootDir) {
|
|
28343
28346
|
const path4 = deployConfigPath(rootDir);
|
|
28344
|
-
if (!
|
|
28347
|
+
if (!existsSync14(path4)) {
|
|
28345
28348
|
throw new Error(`Missing ${DEPLOY_CONFIG_FILE} at ${path4}`);
|
|
28346
28349
|
}
|
|
28347
28350
|
const raw = readFileSync12(path4, "utf-8");
|
|
@@ -28351,14 +28354,25 @@ function loadDeployConfig(rootDir) {
|
|
|
28351
28354
|
} catch (e) {
|
|
28352
28355
|
throw new Error(`Invalid JSON in ${DEPLOY_CONFIG_FILE}: ${e.message}`);
|
|
28353
28356
|
}
|
|
28357
|
+
const envNames = isObject(parsed) && isObject(parsed.envs) ? Object.keys(parsed.envs) : [];
|
|
28358
|
+
const envFiles = loadDeployEnvFiles(rootDir, envNames);
|
|
28359
|
+
applyDeployGlobals(envFiles.globals);
|
|
28354
28360
|
const expanded = expandEnvVars(parsed, process.env);
|
|
28355
|
-
|
|
28361
|
+
const validated = validateDeployConfig(expanded);
|
|
28362
|
+
for (const [envName, vars] of Object.entries(envFiles.perEnv)) {
|
|
28363
|
+
if (!(envName in validated.envs))
|
|
28364
|
+
continue;
|
|
28365
|
+
const env2 = validated.envs[envName];
|
|
28366
|
+
const merged = { ...vars, ...env2.envVars ?? {} };
|
|
28367
|
+
validated.envs[envName] = { ...env2, envVars: merged };
|
|
28368
|
+
}
|
|
28369
|
+
return validated;
|
|
28356
28370
|
}
|
|
28357
28371
|
function saveDeployConfig(rootDir, cfg) {
|
|
28358
28372
|
const path4 = deployConfigPath(rootDir);
|
|
28359
|
-
const raw =
|
|
28373
|
+
const raw = existsSync14(path4) ? JSON.parse(readFileSync12(path4, "utf-8")) : {};
|
|
28360
28374
|
raw.target = { ...raw.target, ...cfg.target };
|
|
28361
|
-
|
|
28375
|
+
writeFileSync12(path4, JSON.stringify(raw, null, 2) + `
|
|
28362
28376
|
`);
|
|
28363
28377
|
}
|
|
28364
28378
|
var VAR_REGEX = /\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g;
|
|
@@ -28384,6 +28398,15 @@ function validateDeployConfig(input) {
|
|
|
28384
28398
|
const target = requireObject(input, "target");
|
|
28385
28399
|
const envs = requireObject(input, "envs");
|
|
28386
28400
|
const caddy = requireObject(input, "caddy");
|
|
28401
|
+
const registry = requireObject(input, "registry");
|
|
28402
|
+
const registryDomain = requireString(registry, "registry.domain");
|
|
28403
|
+
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(registryDomain)) {
|
|
28404
|
+
throw new Error(`deploy.arc.json: registry.domain "${registryDomain}" doesn't look like a domain`);
|
|
28405
|
+
}
|
|
28406
|
+
const passwordEnv = requireString(registry, "registry.passwordEnv");
|
|
28407
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(passwordEnv)) {
|
|
28408
|
+
throw new Error(`deploy.arc.json: registry.passwordEnv "${passwordEnv}" must be an UPPER_SNAKE_CASE env var name`);
|
|
28409
|
+
}
|
|
28387
28410
|
const validated = {
|
|
28388
28411
|
target: {
|
|
28389
28412
|
host: requireString(target, "target.host"),
|
|
@@ -28395,6 +28418,11 @@ function validateDeployConfig(input) {
|
|
|
28395
28418
|
envs: {},
|
|
28396
28419
|
caddy: {
|
|
28397
28420
|
email: requireString(caddy, "caddy.email")
|
|
28421
|
+
},
|
|
28422
|
+
registry: {
|
|
28423
|
+
domain: registryDomain,
|
|
28424
|
+
username: optionalString(registry, "registry.username") ?? "deploy",
|
|
28425
|
+
passwordEnv
|
|
28398
28426
|
}
|
|
28399
28427
|
};
|
|
28400
28428
|
const envKeys = Object.keys(envs);
|
|
@@ -28510,25 +28538,48 @@ function cfgErr(path4, expected) {
|
|
|
28510
28538
|
|
|
28511
28539
|
// src/deploy/ssh.ts
|
|
28512
28540
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
28541
|
+
import { existsSync as existsSync15 } from "fs";
|
|
28542
|
+
import { homedir as homedir3 } from "os";
|
|
28543
|
+
import { join as join16 } from "path";
|
|
28544
|
+
function pickSshKey(target) {
|
|
28545
|
+
if (target.sshKey) {
|
|
28546
|
+
const expanded = target.sshKey.startsWith("~") ? join16(homedir3(), target.sshKey.slice(1)) : target.sshKey;
|
|
28547
|
+
return existsSync15(expanded) ? expanded : null;
|
|
28548
|
+
}
|
|
28549
|
+
for (const name of ["id_ed25519", "id_ecdsa", "id_rsa"]) {
|
|
28550
|
+
const path4 = join16(homedir3(), ".ssh", name);
|
|
28551
|
+
if (existsSync15(path4))
|
|
28552
|
+
return path4;
|
|
28553
|
+
}
|
|
28554
|
+
return null;
|
|
28555
|
+
}
|
|
28513
28556
|
async function streamToString(stream2) {
|
|
28514
28557
|
if (!stream2 || typeof stream2 === "number")
|
|
28515
28558
|
return "";
|
|
28516
28559
|
return new Response(stream2).text();
|
|
28517
28560
|
}
|
|
28518
28561
|
function baseSshArgs(target) {
|
|
28519
|
-
const key = target
|
|
28520
|
-
|
|
28562
|
+
const key = pickSshKey(target);
|
|
28563
|
+
const args = [
|
|
28521
28564
|
"-o",
|
|
28522
28565
|
"BatchMode=yes",
|
|
28523
28566
|
"-o",
|
|
28524
28567
|
"StrictHostKeyChecking=accept-new",
|
|
28525
28568
|
"-o",
|
|
28526
|
-
"
|
|
28527
|
-
"-
|
|
28528
|
-
|
|
28569
|
+
"PreferredAuthentications=publickey",
|
|
28570
|
+
"-o",
|
|
28571
|
+
"ConnectTimeout=5",
|
|
28572
|
+
"-o",
|
|
28573
|
+
"ServerAliveInterval=10",
|
|
28574
|
+
"-o",
|
|
28575
|
+
"ServerAliveCountMax=2",
|
|
28529
28576
|
"-p",
|
|
28530
28577
|
String(target.port)
|
|
28531
28578
|
];
|
|
28579
|
+
if (key) {
|
|
28580
|
+
args.push("-o", "IdentitiesOnly=yes", "-i", key);
|
|
28581
|
+
}
|
|
28582
|
+
return args;
|
|
28532
28583
|
}
|
|
28533
28584
|
async function sshExec(target, cmd, opts = {}) {
|
|
28534
28585
|
const args = [
|
|
@@ -28570,31 +28621,23 @@ async function canSsh(target) {
|
|
|
28570
28621
|
const res = await sshExec(target, "true", { quiet: true });
|
|
28571
28622
|
return res.exitCode === 0;
|
|
28572
28623
|
}
|
|
28573
|
-
async function waitForSsh(target, opts = {}) {
|
|
28574
|
-
const timeout = opts.timeoutMs ?? 300000;
|
|
28575
|
-
const interval = opts.intervalMs ?? 1e4;
|
|
28576
|
-
const start = Date.now();
|
|
28577
|
-
while (Date.now() - start < timeout) {
|
|
28578
|
-
if (await canSsh(target))
|
|
28579
|
-
return;
|
|
28580
|
-
await Bun.sleep(interval);
|
|
28581
|
-
}
|
|
28582
|
-
throw new Error(`Timed out waiting for SSH on ${target.user}@${target.host}`);
|
|
28583
|
-
}
|
|
28584
28624
|
async function scpUpload(target, localPath, remotePath) {
|
|
28585
|
-
const key = target
|
|
28625
|
+
const key = pickSshKey(target);
|
|
28586
28626
|
const args = [
|
|
28587
28627
|
"-o",
|
|
28588
28628
|
"BatchMode=yes",
|
|
28589
28629
|
"-o",
|
|
28590
28630
|
"StrictHostKeyChecking=accept-new",
|
|
28591
28631
|
"-o",
|
|
28592
|
-
"
|
|
28593
|
-
"-
|
|
28594
|
-
|
|
28632
|
+
"PreferredAuthentications=publickey",
|
|
28633
|
+
"-o",
|
|
28634
|
+
"ConnectTimeout=5",
|
|
28595
28635
|
"-P",
|
|
28596
28636
|
String(target.port)
|
|
28597
28637
|
];
|
|
28638
|
+
if (key) {
|
|
28639
|
+
args.push("-o", "IdentitiesOnly=yes", "-i", key);
|
|
28640
|
+
}
|
|
28598
28641
|
args.push(localPath, `${target.user}@${target.host}:${remotePath}`);
|
|
28599
28642
|
const proc2 = spawn3({ cmd: ["scp", ...args], stderr: "pipe" });
|
|
28600
28643
|
const [stderr, exitCode] = await Promise.all([
|
|
@@ -28605,52 +28648,6 @@ async function scpUpload(target, localPath, remotePath) {
|
|
|
28605
28648
|
throw new Error(`scp failed (${exitCode}): ${stderr}`);
|
|
28606
28649
|
}
|
|
28607
28650
|
}
|
|
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
28651
|
|
|
28655
28652
|
// src/deploy/remote-state.ts
|
|
28656
28653
|
var STATE_MARKER_PATH = "/opt/arc/.arc-state.json";
|
|
@@ -28698,6 +28695,18 @@ JSON`);
|
|
|
28698
28695
|
}
|
|
28699
28696
|
|
|
28700
28697
|
// src/deploy/bootstrap.ts
|
|
28698
|
+
async function waitForAnySsh(targets, opts = {}) {
|
|
28699
|
+
const timeout = opts.timeoutMs ?? 300000;
|
|
28700
|
+
const interval = opts.intervalMs ?? 5000;
|
|
28701
|
+
const start = Date.now();
|
|
28702
|
+
while (Date.now() - start < timeout) {
|
|
28703
|
+
const results = await Promise.all(targets.map((t) => canSsh(t)));
|
|
28704
|
+
if (results.some(Boolean))
|
|
28705
|
+
return;
|
|
28706
|
+
await Bun.sleep(interval);
|
|
28707
|
+
}
|
|
28708
|
+
throw new Error(`Timed out waiting for SSH on ${targets.map((t) => `${t.user}@${t.host}`).join(" or ")}`);
|
|
28709
|
+
}
|
|
28701
28710
|
async function bootstrap(inputs) {
|
|
28702
28711
|
const { cfg, state, rootDir } = inputs;
|
|
28703
28712
|
if (state.kind === "unreachable") {
|
|
@@ -28719,12 +28728,16 @@ async function bootstrap(inputs) {
|
|
|
28719
28728
|
cfg.target.host = tfOut.serverIp;
|
|
28720
28729
|
saveDeployConfig(rootDir, cfg);
|
|
28721
28730
|
log2("Waiting for SSH to come up...");
|
|
28722
|
-
await
|
|
28731
|
+
await waitForAnySsh([
|
|
28732
|
+
{ ...cfg.target, user: "root" },
|
|
28733
|
+
{ ...cfg.target, user: cfg.target.user }
|
|
28734
|
+
]);
|
|
28723
28735
|
ok("SSH reachable");
|
|
28724
28736
|
}
|
|
28725
|
-
|
|
28737
|
+
const needAnsible = state.kind === "unreachable" || state.kind === "no-docker" || inputs.forceAnsible === true;
|
|
28738
|
+
if (needAnsible) {
|
|
28726
28739
|
log2("Running Ansible bootstrap (Docker + firewall + SSH hardening)...");
|
|
28727
|
-
const deployUserWorks = state.kind
|
|
28740
|
+
const deployUserWorks = state.kind !== "unreachable" && await canSsh(cfg.target);
|
|
28728
28741
|
const asRoot = !deployUserWorks;
|
|
28729
28742
|
await runAnsible({
|
|
28730
28743
|
target: cfg.target,
|
|
@@ -28733,202 +28746,400 @@ async function bootstrap(inputs) {
|
|
|
28733
28746
|
});
|
|
28734
28747
|
ok("Host bootstrapped");
|
|
28735
28748
|
}
|
|
28736
|
-
|
|
28749
|
+
const needUpStack = state.kind !== "ready" || state.marker === null || state.marker.configHash !== inputs.configHash || !await isRegistryRunning(cfg);
|
|
28750
|
+
if (needUpStack) {
|
|
28737
28751
|
await upStack(inputs);
|
|
28738
28752
|
ok("Docker stack up");
|
|
28739
28753
|
}
|
|
28740
|
-
await writeStateMarker(cfg.target, {
|
|
28741
|
-
cliVersion: inputs.cliVersion,
|
|
28742
|
-
configHash: inputs.configHash,
|
|
28743
|
-
updatedAt: new Date().toISOString()
|
|
28754
|
+
await writeStateMarker(cfg.target, {
|
|
28755
|
+
cliVersion: inputs.cliVersion,
|
|
28756
|
+
configHash: inputs.configHash,
|
|
28757
|
+
updatedAt: new Date().toISOString()
|
|
28758
|
+
});
|
|
28759
|
+
}
|
|
28760
|
+
async function isRegistryRunning(cfg) {
|
|
28761
|
+
const res = await sshExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose ps --status running --format '{{.Service}}' 2>/dev/null || true`, { quiet: true });
|
|
28762
|
+
return res.stdout.split(`
|
|
28763
|
+
`).map((s) => s.trim()).includes("registry");
|
|
28764
|
+
}
|
|
28765
|
+
async function upStack(inputs) {
|
|
28766
|
+
const { cfg } = inputs;
|
|
28767
|
+
const workDir = join17(tmpdir2(), "arc-deploy", `stack-${Date.now()}`);
|
|
28768
|
+
mkdirSync12(workDir, { recursive: true });
|
|
28769
|
+
await assertRegistryDnsResolves(cfg);
|
|
28770
|
+
const password = process.env[cfg.registry.passwordEnv];
|
|
28771
|
+
if (!password) {
|
|
28772
|
+
throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set. ` + `Set it (e.g. \`export ${cfg.registry.passwordEnv}=...\`) before bootstrap.`);
|
|
28773
|
+
}
|
|
28774
|
+
const htpasswdLine = await generateHtpasswd(cfg.registry.username, password);
|
|
28775
|
+
writeFileSync13(join17(workDir, "htpasswd"), htpasswdLine);
|
|
28776
|
+
writeFileSync13(join17(workDir, "Caddyfile"), generateCaddyfile(cfg));
|
|
28777
|
+
writeFileSync13(join17(workDir, "docker-compose.yml"), generateCompose({ cfg }));
|
|
28778
|
+
await assertExec(cfg.target, `sudo mkdir -p ${cfg.target.remoteDir} && sudo chown ${cfg.target.user}:${cfg.target.user} ${cfg.target.remoteDir}`);
|
|
28779
|
+
for (const name of Object.keys(cfg.envs)) {
|
|
28780
|
+
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/${name}`);
|
|
28781
|
+
}
|
|
28782
|
+
await assertExec(cfg.target, `mkdir -p ${cfg.target.remoteDir}/registry-auth`);
|
|
28783
|
+
await scpUpload(cfg.target, join17(workDir, "Caddyfile"), `${cfg.target.remoteDir}/Caddyfile`);
|
|
28784
|
+
await scpUpload(cfg.target, join17(workDir, "docker-compose.yml"), `${cfg.target.remoteDir}/docker-compose.yml`);
|
|
28785
|
+
await scpUpload(cfg.target, join17(workDir, "htpasswd"), `${cfg.target.remoteDir}/registry-auth/htpasswd`);
|
|
28786
|
+
await assertExec(cfg.target, `touch ${cfg.target.remoteDir}/.env`);
|
|
28787
|
+
await assertExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose pull --ignore-pull-failures caddy registry && docker compose up -d caddy registry`);
|
|
28788
|
+
await sshDockerLogin(cfg);
|
|
28789
|
+
const knownEnvs = await listConfiguredEnvs(cfg);
|
|
28790
|
+
if (knownEnvs.length > 0) {
|
|
28791
|
+
await assertExec(cfg.target, `cd ${cfg.target.remoteDir} && docker compose up -d ${knownEnvs.map((e) => `arc-${e}`).join(" ")}`);
|
|
28792
|
+
}
|
|
28793
|
+
}
|
|
28794
|
+
async function sshDockerLogin(cfg) {
|
|
28795
|
+
const password = process.env[cfg.registry.passwordEnv];
|
|
28796
|
+
if (!password) {
|
|
28797
|
+
throw new Error(`Registry password env var ${cfg.registry.passwordEnv} is not set on the deploy host (CLI machine).`);
|
|
28798
|
+
}
|
|
28799
|
+
const cmd = `docker login ${cfg.registry.domain} -u ${cfg.registry.username} --password-stdin`;
|
|
28800
|
+
const proc2 = spawn4({
|
|
28801
|
+
cmd: [
|
|
28802
|
+
"ssh",
|
|
28803
|
+
...baseSshArgs(cfg.target),
|
|
28804
|
+
`${cfg.target.user}@${cfg.target.host}`,
|
|
28805
|
+
"--",
|
|
28806
|
+
cmd
|
|
28807
|
+
],
|
|
28808
|
+
stdin: "pipe",
|
|
28809
|
+
stdout: "pipe",
|
|
28810
|
+
stderr: "pipe"
|
|
28811
|
+
});
|
|
28812
|
+
if (proc2.stdin) {
|
|
28813
|
+
await proc2.stdin.write(new TextEncoder().encode(password));
|
|
28814
|
+
await proc2.stdin.end?.();
|
|
28815
|
+
}
|
|
28816
|
+
const exit = await proc2.exited;
|
|
28817
|
+
if (exit !== 0) {
|
|
28818
|
+
const stderr = await new Response(proc2.stderr).text();
|
|
28819
|
+
throw new Error(`Server-side docker login failed (exit ${exit}): ${stderr.trim()}`);
|
|
28820
|
+
}
|
|
28821
|
+
}
|
|
28822
|
+
async function listConfiguredEnvs(cfg) {
|
|
28823
|
+
const res = await sshExec(cfg.target, `cat ${cfg.target.remoteDir}/.env 2>/dev/null || true`, { quiet: true });
|
|
28824
|
+
const set = new Set;
|
|
28825
|
+
for (const line of res.stdout.split(`
|
|
28826
|
+
`)) {
|
|
28827
|
+
const m = line.match(/^ARC_IMAGE_([A-Z0-9_]+)=/);
|
|
28828
|
+
if (!m)
|
|
28829
|
+
continue;
|
|
28830
|
+
const lowerName = m[1].toLowerCase().replace(/_/g, "-");
|
|
28831
|
+
if (lowerName in cfg.envs)
|
|
28832
|
+
set.add(lowerName);
|
|
28833
|
+
}
|
|
28834
|
+
return [...set];
|
|
28835
|
+
}
|
|
28836
|
+
async function assertRegistryDnsResolves(cfg) {
|
|
28837
|
+
const apex = apexDomain(cfg.registry.domain);
|
|
28838
|
+
let nameservers = await digQuery("8.8.8.8", "NS", apex);
|
|
28839
|
+
nameservers = nameservers.map((s) => s.replace(/\.$/, ""));
|
|
28840
|
+
const sources = [...nameservers, "1.1.1.1", "8.8.8.8"];
|
|
28841
|
+
let lastAnswers = [];
|
|
28842
|
+
for (const source of sources) {
|
|
28843
|
+
const answers = await digQuery(source, "A", cfg.registry.domain);
|
|
28844
|
+
if (answers.length === 0)
|
|
28845
|
+
continue;
|
|
28846
|
+
lastAnswers = answers;
|
|
28847
|
+
if (answers.includes(cfg.target.host))
|
|
28848
|
+
return;
|
|
28849
|
+
}
|
|
28850
|
+
if (lastAnswers.length === 0) {
|
|
28851
|
+
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.`);
|
|
28852
|
+
}
|
|
28853
|
+
throw new Error(`Registry DNS mismatch: ${cfg.registry.domain} resolves to [${lastAnswers.join(", ")}], ` + `but target host is ${cfg.target.host}. Update the A record before continuing.`);
|
|
28854
|
+
}
|
|
28855
|
+
function apexDomain(host) {
|
|
28856
|
+
const parts = host.split(".");
|
|
28857
|
+
return parts.slice(-2).join(".");
|
|
28858
|
+
}
|
|
28859
|
+
async function digQuery(server, type, name) {
|
|
28860
|
+
const proc2 = spawn4({
|
|
28861
|
+
cmd: ["dig", `@${server}`, "+short", "+time=3", "+tries=1", type, name],
|
|
28862
|
+
stdout: "pipe",
|
|
28863
|
+
stderr: "ignore"
|
|
28744
28864
|
});
|
|
28865
|
+
const exit = await proc2.exited;
|
|
28866
|
+
if (exit !== 0)
|
|
28867
|
+
return [];
|
|
28868
|
+
return (await new Response(proc2.stdout).text()).split(`
|
|
28869
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
28870
|
+
}
|
|
28871
|
+
|
|
28872
|
+
// src/deploy/deploy-env.ts
|
|
28873
|
+
var HEALTH_RETRIES = 10;
|
|
28874
|
+
var HEALTH_DELAY_MS = 1000;
|
|
28875
|
+
async function updateEnvDeployment(opts) {
|
|
28876
|
+
const { target, cfg, env: env2, fullRef } = opts;
|
|
28877
|
+
const upperEnv = env2.toUpperCase().replace(/-/g, "_");
|
|
28878
|
+
const envVarName = `ARC_IMAGE_${upperEnv}`;
|
|
28879
|
+
const envPath = `${cfg.target.remoteDir}/.env`;
|
|
28880
|
+
const escapedRef = fullRef.replace(/"/g, "\\\"");
|
|
28881
|
+
const updateScript = [
|
|
28882
|
+
`touch ${envPath} && `,
|
|
28883
|
+
`awk -v line="${envVarName}=${escapedRef}" -v key="${envVarName}=" '`,
|
|
28884
|
+
` BEGIN { replaced=0 } `,
|
|
28885
|
+
` $0 ~ "^"key { print line; replaced=1; next } `,
|
|
28886
|
+
` { print } `,
|
|
28887
|
+
` END { if (!replaced) print line } `,
|
|
28888
|
+
`' ${envPath} > ${envPath}.tmp && mv ${envPath}.tmp ${envPath}`
|
|
28889
|
+
].join("");
|
|
28890
|
+
await assertExec(target, updateScript);
|
|
28891
|
+
await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose pull arc-${env2}`);
|
|
28892
|
+
await assertExec(target, `cd ${cfg.target.remoteDir} && docker compose up -d arc-${env2}`);
|
|
28893
|
+
const retain = opts.retainImages ?? 3;
|
|
28894
|
+
const imageBaseName = imageBaseFromRef(fullRef);
|
|
28895
|
+
if (imageBaseName) {
|
|
28896
|
+
const pruneScript = [
|
|
28897
|
+
`docker images "${imageBaseName}" --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" `,
|
|
28898
|
+
`| grep -v ":latest " `,
|
|
28899
|
+
`| sort -k2,3 -r `,
|
|
28900
|
+
`| tail -n +${retain + 1} `,
|
|
28901
|
+
`| awk '{print $1}' `,
|
|
28902
|
+
`| xargs -r docker rmi 2>/dev/null || true`
|
|
28903
|
+
].join("");
|
|
28904
|
+
await sshExec(target, pruneScript, { quiet: true });
|
|
28905
|
+
}
|
|
28906
|
+
const ok2 = await healthCheck(target, env2);
|
|
28907
|
+
return { env: env2, fullRef, redeployed: ok2 };
|
|
28908
|
+
}
|
|
28909
|
+
function imageBaseFromRef(fullRef) {
|
|
28910
|
+
const colonIdx = fullRef.lastIndexOf(":");
|
|
28911
|
+
return colonIdx > 0 ? fullRef.slice(0, colonIdx) : null;
|
|
28912
|
+
}
|
|
28913
|
+
async function healthCheck(target, env2) {
|
|
28914
|
+
for (let i = 0;i < HEALTH_RETRIES; i++) {
|
|
28915
|
+
const res = await sshExec(target, `docker exec arc-${env2} wget -qO- http://localhost:5005/health 2>&1 || echo HEALTHCHECK_FAILED`, { quiet: true });
|
|
28916
|
+
if (res.exitCode === 0 && !res.stdout.includes("HEALTHCHECK_FAILED")) {
|
|
28917
|
+
return true;
|
|
28918
|
+
}
|
|
28919
|
+
await sleep(HEALTH_DELAY_MS);
|
|
28920
|
+
}
|
|
28921
|
+
return false;
|
|
28745
28922
|
}
|
|
28746
|
-
|
|
28747
|
-
|
|
28748
|
-
|
|
28749
|
-
|
|
28750
|
-
|
|
28751
|
-
|
|
28752
|
-
|
|
28753
|
-
|
|
28754
|
-
|
|
28923
|
+
function sleep(ms) {
|
|
28924
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
28925
|
+
}
|
|
28926
|
+
|
|
28927
|
+
// src/deploy/image.ts
|
|
28928
|
+
var {spawn: spawn5 } = globalThis.Bun;
|
|
28929
|
+
import { createHash as createHash3 } from "crypto";
|
|
28930
|
+
import {
|
|
28931
|
+
copyFileSync as copyFileSync2,
|
|
28932
|
+
existsSync as existsSync16,
|
|
28933
|
+
mkdirSync as mkdirSync13,
|
|
28934
|
+
readFileSync as readFileSync13,
|
|
28935
|
+
realpathSync as realpathSync2,
|
|
28936
|
+
writeFileSync as writeFileSync14
|
|
28937
|
+
} from "fs";
|
|
28938
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
28939
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
28940
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
28941
|
+
|
|
28942
|
+
// src/deploy/image-template.ts
|
|
28943
|
+
function generateDockerfile(inputs) {
|
|
28944
|
+
const conditionalCopies = [];
|
|
28945
|
+
if (inputs.hasPublicDir) {
|
|
28946
|
+
conditionalCopies.push("COPY public/ /app/public/");
|
|
28947
|
+
}
|
|
28948
|
+
if (inputs.hasManifest && inputs.manifestPath) {
|
|
28949
|
+
conditionalCopies.push(`COPY ${inputs.manifestPath} /app/${inputs.manifestPath}`);
|
|
28950
|
+
}
|
|
28951
|
+
if (inputs.hasLocales) {
|
|
28952
|
+
conditionalCopies.push("COPY locales/ /app/locales/");
|
|
28755
28953
|
}
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28954
|
+
return [
|
|
28955
|
+
"# Generated by `arc platform deploy` \u2014 do not edit by hand.",
|
|
28956
|
+
"FROM oven/bun:1-alpine",
|
|
28957
|
+
"",
|
|
28958
|
+
"RUN apk add --no-cache tini ca-certificates",
|
|
28959
|
+
"",
|
|
28960
|
+
"WORKDIR /app",
|
|
28961
|
+
"",
|
|
28962
|
+
"# Layer 1 \u2014 framework peers as /app/package.json + install into /app/node_modules.",
|
|
28963
|
+
"# resolveWorkspace() walks up looking for the first package.json (finds /app/),",
|
|
28964
|
+
"# loadServerContext resolves @arcote.tech/platform from /app/node_modules.",
|
|
28965
|
+
"# Cached, invalidates only on .arc/platform/package.json change.",
|
|
28966
|
+
"COPY .arc/platform/package.json /app/package.json",
|
|
28967
|
+
"RUN cd /app && bun install --production --no-save",
|
|
28968
|
+
"",
|
|
28969
|
+
"# Layer 2 \u2014 user artifacts (chunks, shell, styles, server bundles, host.js).",
|
|
28970
|
+
"# host.js IS the arc-cli bundle \u2014 `arc platform deploy` copied it here from",
|
|
28971
|
+
"# whatever arc-cli was on the user's PATH. No npm dependency at runtime.",
|
|
28972
|
+
"COPY .arc/platform/ /app/.arc/platform/",
|
|
28973
|
+
...conditionalCopies,
|
|
28974
|
+
"",
|
|
28975
|
+
"ENV PORT=5005",
|
|
28976
|
+
"ENV NODE_ENV=production",
|
|
28977
|
+
"EXPOSE 5005",
|
|
28978
|
+
"",
|
|
28979
|
+
'ENTRYPOINT ["tini", "--"]',
|
|
28980
|
+
'CMD ["bun", "run", "/app/.arc/platform/host.js", "platform", "start"]',
|
|
28981
|
+
""
|
|
28982
|
+
].join(`
|
|
28983
|
+
`);
|
|
28759
28984
|
}
|
|
28760
28985
|
|
|
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
|
-
const
|
|
28811
|
-
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
|
|
28819
|
-
|
|
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);
|
|
28986
|
+
// src/deploy/image.ts
|
|
28987
|
+
async function buildImage(ws, opts) {
|
|
28988
|
+
await ensureDocker();
|
|
28989
|
+
const manifestPath = join18(ws.arcDir, "manifest.json");
|
|
28990
|
+
if (!existsSync16(manifestPath)) {
|
|
28991
|
+
throw new Error(`No build manifest at ${manifestPath}. Run \`arc platform build\` first or omit --skip-build.`);
|
|
28992
|
+
}
|
|
28993
|
+
embedCliBundle(ws);
|
|
28994
|
+
const contentHash = computeContentHash(manifestPath);
|
|
28995
|
+
const imageTag = `${opts.imageName}:${contentHash}`;
|
|
28996
|
+
const fullRef = opts.registryDomain ? `${opts.registryDomain}/${imageTag}` : imageTag;
|
|
28997
|
+
const dockerfileInputs = collectDockerfileInputs(ws);
|
|
28998
|
+
const dockerfile = generateDockerfile(dockerfileInputs);
|
|
28999
|
+
const buildContextDir = ws.rootDir;
|
|
29000
|
+
const dockerfileDir = join18(tmpdir3(), `arc-image-${Date.now()}`);
|
|
29001
|
+
mkdirSync13(dockerfileDir, { recursive: true });
|
|
29002
|
+
const dockerfilePath = join18(dockerfileDir, "Dockerfile");
|
|
29003
|
+
writeFileSync14(dockerfilePath, dockerfile);
|
|
29004
|
+
const buildArgs = [
|
|
29005
|
+
"build",
|
|
29006
|
+
"--platform=linux/amd64",
|
|
29007
|
+
"-f",
|
|
29008
|
+
dockerfilePath,
|
|
29009
|
+
"-t",
|
|
29010
|
+
fullRef,
|
|
29011
|
+
"-t",
|
|
29012
|
+
`${opts.imageName}:latest`,
|
|
29013
|
+
buildContextDir
|
|
29014
|
+
];
|
|
29015
|
+
const proc2 = spawn5({
|
|
29016
|
+
cmd: ["docker", ...buildArgs],
|
|
29017
|
+
stdout: "inherit",
|
|
29018
|
+
stderr: "inherit"
|
|
29019
|
+
});
|
|
29020
|
+
const exit = await proc2.exited;
|
|
29021
|
+
if (exit !== 0) {
|
|
29022
|
+
throw new Error(`docker build failed (exit ${exit})`);
|
|
29023
|
+
}
|
|
29024
|
+
return { imageTag, fullRef, contentHash };
|
|
29025
|
+
}
|
|
29026
|
+
function embedCliBundle(ws) {
|
|
29027
|
+
const source = locateCliBundle();
|
|
29028
|
+
const target = join18(ws.arcDir, "host.js");
|
|
29029
|
+
copyFileSync2(source, target);
|
|
29030
|
+
}
|
|
29031
|
+
function locateCliBundle() {
|
|
29032
|
+
const here = fileURLToPath5(import.meta.url);
|
|
29033
|
+
let cur = dirname7(here);
|
|
29034
|
+
while (cur !== "/" && cur !== "") {
|
|
29035
|
+
const candidate = join18(cur, "package.json");
|
|
29036
|
+
if (existsSync16(candidate)) {
|
|
29037
|
+
try {
|
|
29038
|
+
const pkg = JSON.parse(readFileSync13(candidate, "utf-8"));
|
|
29039
|
+
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
29040
|
+
const distIndex = join18(realpathSync2(cur), "dist", "index.js");
|
|
29041
|
+
if (!existsSync16(distIndex)) {
|
|
29042
|
+
throw new Error(`arc-cli bundle missing at ${distIndex}. Run \`bun run build\` in packages/cli/.`);
|
|
29043
|
+
}
|
|
29044
|
+
return distIndex;
|
|
28857
29045
|
}
|
|
29046
|
+
} catch (e) {
|
|
29047
|
+
if (e instanceof Error && e.message.startsWith("arc-cli bundle"))
|
|
29048
|
+
throw e;
|
|
28858
29049
|
}
|
|
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
29050
|
}
|
|
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();
|
|
29051
|
+
const parent = dirname7(cur);
|
|
29052
|
+
if (parent === cur)
|
|
29053
|
+
break;
|
|
29054
|
+
cur = parent;
|
|
28889
29055
|
}
|
|
29056
|
+
throw new Error("Could not locate @arcote.tech/arc-cli package.json walking up from " + here);
|
|
28890
29057
|
}
|
|
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;
|
|
29058
|
+
async function ensureDocker() {
|
|
29059
|
+
try {
|
|
29060
|
+
const proc2 = spawn5({
|
|
29061
|
+
cmd: ["docker", "--version"],
|
|
29062
|
+
stdout: "pipe",
|
|
29063
|
+
stderr: "pipe"
|
|
29064
|
+
});
|
|
29065
|
+
const exit = await proc2.exited;
|
|
29066
|
+
if (exit !== 0)
|
|
29067
|
+
throw new Error("docker --version exited non-zero");
|
|
29068
|
+
} catch {
|
|
29069
|
+
throw new Error("Docker is not available on PATH. Install Docker Desktop (or docker engine + buildx) before running deploy.");
|
|
29070
|
+
}
|
|
29071
|
+
}
|
|
29072
|
+
function computeContentHash(manifestPath) {
|
|
29073
|
+
const raw = JSON.parse(readFileSync13(manifestPath, "utf-8"));
|
|
29074
|
+
delete raw.buildTime;
|
|
29075
|
+
const canonical = JSON.stringify(raw);
|
|
29076
|
+
return createHash3("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
29077
|
+
}
|
|
29078
|
+
function collectDockerfileInputs(ws) {
|
|
29079
|
+
const hasPublicDir = existsSync16(join18(ws.rootDir, "public"));
|
|
29080
|
+
const hasLocales = existsSync16(join18(ws.rootDir, "locales"));
|
|
29081
|
+
let manifestPath;
|
|
29082
|
+
for (const name of ["manifest.webmanifest", "manifest.json"]) {
|
|
29083
|
+
if (existsSync16(join18(ws.rootDir, name))) {
|
|
29084
|
+
manifestPath = name;
|
|
29085
|
+
break;
|
|
28920
29086
|
}
|
|
28921
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
28922
29087
|
}
|
|
28923
|
-
|
|
29088
|
+
return {
|
|
29089
|
+
hasPublicDir,
|
|
29090
|
+
hasManifest: !!manifestPath,
|
|
29091
|
+
manifestPath,
|
|
29092
|
+
hasLocales
|
|
29093
|
+
};
|
|
29094
|
+
}
|
|
29095
|
+
function sanitizeImageName(name) {
|
|
29096
|
+
const cleaned = name.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
29097
|
+
return cleaned || "arc-app";
|
|
29098
|
+
}
|
|
29099
|
+
|
|
29100
|
+
// src/deploy/registry.ts
|
|
29101
|
+
var {spawn: spawn6 } = globalThis.Bun;
|
|
29102
|
+
async function dockerLogin(registry) {
|
|
29103
|
+
const password = process.env[registry.passwordEnv];
|
|
29104
|
+
if (!password) {
|
|
29105
|
+
throw new Error(`Registry password env var ${registry.passwordEnv} is not set. ` + `Set it in your shell before running deploy.`);
|
|
29106
|
+
}
|
|
29107
|
+
const proc2 = spawn6({
|
|
29108
|
+
cmd: [
|
|
29109
|
+
"docker",
|
|
29110
|
+
"login",
|
|
29111
|
+
registry.domain,
|
|
29112
|
+
"-u",
|
|
29113
|
+
registry.username,
|
|
29114
|
+
"--password-stdin"
|
|
29115
|
+
],
|
|
29116
|
+
stdin: "pipe",
|
|
29117
|
+
stdout: "pipe",
|
|
29118
|
+
stderr: "pipe"
|
|
29119
|
+
});
|
|
29120
|
+
proc2.stdin.write(password);
|
|
29121
|
+
await proc2.stdin.end();
|
|
29122
|
+
const exit = await proc2.exited;
|
|
29123
|
+
if (exit !== 0) {
|
|
29124
|
+
const stderr = await new Response(proc2.stderr).text();
|
|
29125
|
+
throw new Error(`docker login ${registry.domain} failed (exit ${exit}): ${stderr.trim()}`);
|
|
29126
|
+
}
|
|
28924
29127
|
}
|
|
28925
|
-
function
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
29128
|
+
async function dockerPush(fullRef) {
|
|
29129
|
+
const proc2 = spawn6({
|
|
29130
|
+
cmd: ["docker", "push", fullRef],
|
|
29131
|
+
stdout: "inherit",
|
|
29132
|
+
stderr: "inherit"
|
|
29133
|
+
});
|
|
29134
|
+
const exit = await proc2.exited;
|
|
29135
|
+
if (exit !== 0) {
|
|
29136
|
+
throw new Error(`docker push ${fullRef} failed (exit ${exit})`);
|
|
29137
|
+
}
|
|
28930
29138
|
}
|
|
28931
29139
|
|
|
29140
|
+
// ../../node_modules/.bun/@clack+prompts@0.9.1/node_modules/@clack/prompts/dist/index.mjs
|
|
29141
|
+
import { stripVTControlCharacters as T2 } from "util";
|
|
29142
|
+
|
|
28932
29143
|
// ../../node_modules/.bun/@clack+core@0.4.1/node_modules/@clack/core/dist/index.mjs
|
|
28933
29144
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
28934
29145
|
import { stdin as $, stdout as j } from "process";
|
|
@@ -29452,6 +29663,21 @@ ${import_picocolors2.default.cyan(m2)}
|
|
|
29452
29663
|
}
|
|
29453
29664
|
} }).prompt();
|
|
29454
29665
|
};
|
|
29666
|
+
var ye = (s = "", n = "") => {
|
|
29667
|
+
const t = `
|
|
29668
|
+
${s}
|
|
29669
|
+
`.split(`
|
|
29670
|
+
`), i = T2(n).length, r2 = Math.max(t.reduce((o, l2) => {
|
|
29671
|
+
const $2 = T2(l2);
|
|
29672
|
+
return $2.length > o ? $2.length : o;
|
|
29673
|
+
}, 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(`
|
|
29674
|
+
`);
|
|
29675
|
+
process.stdout.write(`${import_picocolors2.default.gray(a)}
|
|
29676
|
+
${import_picocolors2.default.green(S2)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(N2.repeat(Math.max(r2 - i - 1, 1)) + re)}
|
|
29677
|
+
${c2}
|
|
29678
|
+
${import_picocolors2.default.gray(ie + N2.repeat(r2 + 2) + ne)}
|
|
29679
|
+
`);
|
|
29680
|
+
};
|
|
29455
29681
|
var ve = (s = "") => {
|
|
29456
29682
|
process.stdout.write(`${import_picocolors2.default.gray(m2)} ${import_picocolors2.default.red(s)}
|
|
29457
29683
|
|
|
@@ -29593,6 +29819,45 @@ async function runSurvey() {
|
|
|
29593
29819
|
});
|
|
29594
29820
|
if (BD(email))
|
|
29595
29821
|
cancel();
|
|
29822
|
+
ye(`The host runs a private Docker registry behind Caddy.
|
|
29823
|
+
Create an A record for the registry domain pointing to your host before deploy.`, "Registry");
|
|
29824
|
+
const registryDomain = await ue({
|
|
29825
|
+
message: "Registry domain (full FQDN)",
|
|
29826
|
+
placeholder: "registry.example.com",
|
|
29827
|
+
validate: (v2) => /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(v2) ? undefined : "Expected a domain"
|
|
29828
|
+
});
|
|
29829
|
+
if (BD(registryDomain))
|
|
29830
|
+
cancel();
|
|
29831
|
+
const registryUser = await ue({
|
|
29832
|
+
message: "Registry username",
|
|
29833
|
+
initialValue: "deploy",
|
|
29834
|
+
validate: (v2) => /^[a-z_][a-z0-9_-]*$/.test(v2) ? undefined : "Invalid username"
|
|
29835
|
+
});
|
|
29836
|
+
if (BD(registryUser))
|
|
29837
|
+
cancel();
|
|
29838
|
+
const registryPasswordEnv = await ue({
|
|
29839
|
+
message: "Env var holding the registry password",
|
|
29840
|
+
initialValue: "ARC_REGISTRY_PASSWORD",
|
|
29841
|
+
validate: (v2) => /^[A-Z][A-Z0-9_]*$/.test(v2) ? undefined : "Must be UPPER_SNAKE_CASE"
|
|
29842
|
+
});
|
|
29843
|
+
if (BD(registryPasswordEnv))
|
|
29844
|
+
cancel();
|
|
29845
|
+
const generatePassword = await me({
|
|
29846
|
+
message: "Generate a random password now?",
|
|
29847
|
+
initialValue: true
|
|
29848
|
+
});
|
|
29849
|
+
if (BD(generatePassword))
|
|
29850
|
+
cancel();
|
|
29851
|
+
if (generatePassword) {
|
|
29852
|
+
const random = generateRandomPassword(32);
|
|
29853
|
+
ye(`Save this password \u2014 you'll need it on every deploy.
|
|
29854
|
+
|
|
29855
|
+
export ${registryPasswordEnv}=${random}
|
|
29856
|
+
|
|
29857
|
+
This prompt is the only time it's shown.`, "Registry password");
|
|
29858
|
+
} else {
|
|
29859
|
+
ye(`Set ${registryPasswordEnv} in your shell before running deploy.`, "Registry password");
|
|
29860
|
+
}
|
|
29596
29861
|
fe("Configuration ready \u2014 writing deploy.arc.json");
|
|
29597
29862
|
return {
|
|
29598
29863
|
target: {
|
|
@@ -29603,9 +29868,19 @@ async function runSurvey() {
|
|
|
29603
29868
|
},
|
|
29604
29869
|
envs,
|
|
29605
29870
|
caddy: { email },
|
|
29871
|
+
registry: {
|
|
29872
|
+
domain: registryDomain,
|
|
29873
|
+
username: registryUser,
|
|
29874
|
+
passwordEnv: registryPasswordEnv
|
|
29875
|
+
},
|
|
29606
29876
|
provision
|
|
29607
29877
|
};
|
|
29608
29878
|
}
|
|
29879
|
+
function generateRandomPassword(bytes) {
|
|
29880
|
+
const buf = new Uint8Array(bytes);
|
|
29881
|
+
crypto.getRandomValues(buf);
|
|
29882
|
+
return btoa(String.fromCharCode(...buf)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
29883
|
+
}
|
|
29609
29884
|
function cancel() {
|
|
29610
29885
|
ve("Cancelled.");
|
|
29611
29886
|
process.exit(0);
|
|
@@ -29626,82 +29901,106 @@ async function platformDeploy(envArg, options = {}) {
|
|
|
29626
29901
|
err(`Unknown env "${envArg}". Known: ${Object.keys(cfg.envs).join(", ")}`);
|
|
29627
29902
|
process.exit(1);
|
|
29628
29903
|
})() : Object.keys(cfg.envs);
|
|
29629
|
-
const manifestPath =
|
|
29630
|
-
|
|
29631
|
-
|
|
29632
|
-
|
|
29633
|
-
|
|
29634
|
-
|
|
29635
|
-
|
|
29636
|
-
|
|
29637
|
-
|
|
29904
|
+
const manifestPath = join19(ws.arcDir, "manifest.json");
|
|
29905
|
+
if (!options.imageTag) {
|
|
29906
|
+
const needBuild = options.rebuild || !existsSync17(manifestPath);
|
|
29907
|
+
if (needBuild && !options.skipBuild) {
|
|
29908
|
+
log2("Building platform...");
|
|
29909
|
+
await buildAll(ws, { noCache: options.rebuild });
|
|
29910
|
+
ok("Build complete");
|
|
29911
|
+
} else if (!existsSync17(manifestPath)) {
|
|
29912
|
+
err("No build found and --skip-build was set.");
|
|
29913
|
+
process.exit(1);
|
|
29914
|
+
}
|
|
29915
|
+
}
|
|
29916
|
+
const imageName = sanitizeImageName(ws.rootPkg.name ?? ws.appName);
|
|
29917
|
+
let fullRef;
|
|
29918
|
+
let contentHash;
|
|
29919
|
+
if (options.imageTag) {
|
|
29920
|
+
contentHash = options.imageTag.includes(":") ? options.imageTag.split(":").pop() : options.imageTag;
|
|
29921
|
+
fullRef = `${cfg.registry.domain}/${imageName}:${contentHash}`;
|
|
29922
|
+
log2(`Pinning to existing image ${fullRef} (skipping build + push)`);
|
|
29923
|
+
} else {
|
|
29924
|
+
log2(`Building Docker image ${imageName}...`);
|
|
29925
|
+
const result = await buildImage(ws, {
|
|
29926
|
+
imageName,
|
|
29927
|
+
registryDomain: cfg.registry.domain
|
|
29928
|
+
});
|
|
29929
|
+
fullRef = result.fullRef;
|
|
29930
|
+
contentHash = result.contentHash;
|
|
29931
|
+
ok(`Image built: ${fullRef}`);
|
|
29932
|
+
if (options.buildOnly) {
|
|
29933
|
+
log2(`contentHash: ${contentHash}`);
|
|
29934
|
+
return;
|
|
29935
|
+
}
|
|
29638
29936
|
}
|
|
29639
29937
|
log2("Inspecting remote server...");
|
|
29640
29938
|
const state = await detectRemoteState(cfg);
|
|
29641
29939
|
log2(`Remote state: ${state.kind}`);
|
|
29642
29940
|
const cliVersion = readCliVersion();
|
|
29643
29941
|
const configHash = await hashDeployConfig(ws.rootDir);
|
|
29644
|
-
|
|
29645
|
-
|
|
29646
|
-
|
|
29647
|
-
|
|
29648
|
-
|
|
29649
|
-
|
|
29650
|
-
|
|
29651
|
-
|
|
29942
|
+
await bootstrap({
|
|
29943
|
+
cfg,
|
|
29944
|
+
rootDir: ws.rootDir,
|
|
29945
|
+
state,
|
|
29946
|
+
cliVersion,
|
|
29947
|
+
configHash,
|
|
29948
|
+
forceAnsible: options.forceBootstrap
|
|
29949
|
+
});
|
|
29950
|
+
if (!options.imageTag) {
|
|
29951
|
+
log2(`Logging in to ${cfg.registry.domain}...`);
|
|
29952
|
+
await dockerLogin(cfg.registry);
|
|
29953
|
+
log2(`Pushing ${fullRef}...`);
|
|
29954
|
+
await dockerPush(fullRef);
|
|
29955
|
+
ok("Image pushed");
|
|
29652
29956
|
}
|
|
29653
29957
|
for (const env2 of targetEnvs) {
|
|
29654
|
-
log2(`
|
|
29655
|
-
const outcome = await
|
|
29958
|
+
log2(`Updating env "${env2}"...`);
|
|
29959
|
+
const outcome = await updateEnvDeployment({
|
|
29960
|
+
target: cfg.target,
|
|
29656
29961
|
cfg,
|
|
29657
29962
|
env: env2,
|
|
29658
|
-
|
|
29659
|
-
projectDir: ws.rootDir
|
|
29963
|
+
fullRef
|
|
29660
29964
|
});
|
|
29661
|
-
if (outcome.
|
|
29662
|
-
ok(`${env2}:
|
|
29965
|
+
if (outcome.redeployed) {
|
|
29966
|
+
ok(`${env2}: live at ${fullRef}`);
|
|
29663
29967
|
} 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(", ")}`);
|
|
29968
|
+
err(`${env2}: deployed but health check did not pass within retries \u2014 check \`docker logs arc-${env2}\``);
|
|
29673
29969
|
}
|
|
29674
29970
|
}
|
|
29675
29971
|
}
|
|
29676
29972
|
function readCliVersion() {
|
|
29677
|
-
const candidates = [];
|
|
29678
|
-
const entry = process.argv[1];
|
|
29679
|
-
if (entry) {
|
|
29680
|
-
candidates.push(join18(dirname7(entry), "..", "package.json"));
|
|
29681
|
-
}
|
|
29682
29973
|
try {
|
|
29683
|
-
|
|
29684
|
-
|
|
29685
|
-
|
|
29686
|
-
|
|
29687
|
-
|
|
29688
|
-
|
|
29689
|
-
|
|
29690
|
-
|
|
29691
|
-
|
|
29974
|
+
let cur = dirname8(fileURLToPath6(import.meta.url));
|
|
29975
|
+
const root = dirname8(cur).startsWith("/") ? "/" : ".";
|
|
29976
|
+
while (cur !== root && cur !== "") {
|
|
29977
|
+
const candidate = join19(cur, "package.json");
|
|
29978
|
+
if (existsSync17(candidate)) {
|
|
29979
|
+
const pkg = JSON.parse(readFileSync14(candidate, "utf-8"));
|
|
29980
|
+
if (pkg.name === "@arcote.tech/arc-cli") {
|
|
29981
|
+
return pkg.version ?? "unknown";
|
|
29982
|
+
}
|
|
29983
|
+
}
|
|
29984
|
+
const parent = dirname8(cur);
|
|
29985
|
+
if (parent === cur)
|
|
29986
|
+
break;
|
|
29987
|
+
cur = parent;
|
|
29988
|
+
}
|
|
29989
|
+
return "unknown";
|
|
29990
|
+
} catch {
|
|
29991
|
+
return "unknown";
|
|
29692
29992
|
}
|
|
29693
|
-
return "unknown";
|
|
29694
29993
|
}
|
|
29695
29994
|
async function hashDeployConfig(rootDir) {
|
|
29696
|
-
const p2 =
|
|
29995
|
+
const p2 = join19(rootDir, "deploy.arc.json");
|
|
29697
29996
|
const content = readFileSync14(p2);
|
|
29698
29997
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
29699
29998
|
hasher.update(content);
|
|
29700
29999
|
return hasher.digest("hex").slice(0, 16);
|
|
29701
30000
|
}
|
|
29702
30001
|
|
|
29703
|
-
// src/
|
|
29704
|
-
import { existsSync as
|
|
30002
|
+
// src/platform/startup.ts
|
|
30003
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, watch } from "fs";
|
|
29705
30004
|
import { join as join21 } from "path";
|
|
29706
30005
|
|
|
29707
30006
|
// ../host/src/create-server.ts
|
|
@@ -30162,8 +30461,8 @@ var d2 = class {
|
|
|
30162
30461
|
throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part (" + this[e2].length + ")");
|
|
30163
30462
|
if (c2 > w2)
|
|
30164
30463
|
throw new TypeError("CronPattern: From value is larger than to value: '" + t + "'");
|
|
30165
|
-
for (let
|
|
30166
|
-
this.setPart(e2,
|
|
30464
|
+
for (let T3 = c2;T3 <= w2; T3 += C2)
|
|
30465
|
+
this.setPart(e2, T3, i[1] || s);
|
|
30167
30466
|
}
|
|
30168
30467
|
extractNth(t, e2) {
|
|
30169
30468
|
let r2 = t, s;
|
|
@@ -31152,248 +31451,14 @@ async function createArcServer(config) {
|
|
|
31152
31451
|
};
|
|
31153
31452
|
}
|
|
31154
31453
|
// src/platform/server.ts
|
|
31155
|
-
|
|
31454
|
+
init_i18n();
|
|
31455
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync14 } from "fs";
|
|
31156
31456
|
import { join as join20 } from "path";
|
|
31157
|
-
|
|
31158
|
-
|
|
31159
|
-
|
|
31160
|
-
|
|
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
|
|
31380
|
-
function generateShellHtml(appName, manifest, arcEntries) {
|
|
31381
|
-
const arcImports = {};
|
|
31382
|
-
if (arcEntries) {
|
|
31383
|
-
for (const [short, pkg] of arcEntries) {
|
|
31384
|
-
arcImports[pkg] = `/shell/${short}.js`;
|
|
31385
|
-
}
|
|
31457
|
+
function generateShellHtml(appName, manifest, initial) {
|
|
31458
|
+
const initialUrl = initial ? `/browser/${initial.file}` : null;
|
|
31459
|
+
if (!initialUrl) {
|
|
31460
|
+
throw new Error("generateShellHtml: initial bundle missing from manifest");
|
|
31386
31461
|
}
|
|
31387
|
-
const importMap = {
|
|
31388
|
-
imports: {
|
|
31389
|
-
react: "/shell/react.js",
|
|
31390
|
-
"react/jsx-runtime": "/shell/jsx-runtime.js",
|
|
31391
|
-
"react/jsx-dev-runtime": "/shell/jsx-dev-runtime.js",
|
|
31392
|
-
"react-dom": "/shell/react-dom.js",
|
|
31393
|
-
"react-dom/client": "/shell/react-dom-client.js",
|
|
31394
|
-
...arcImports
|
|
31395
|
-
}
|
|
31396
|
-
};
|
|
31397
31462
|
return `<!doctype html>
|
|
31398
31463
|
<html lang="en">
|
|
31399
31464
|
<head>
|
|
@@ -31404,18 +31469,13 @@ function generateShellHtml(appName, manifest, arcEntries) {
|
|
|
31404
31469
|
<link rel="manifest" href="/manifest.json">` : ""}
|
|
31405
31470
|
<link rel="stylesheet" href="/styles.css" />
|
|
31406
31471
|
<link rel="stylesheet" href="/theme.css" />
|
|
31407
|
-
<
|
|
31472
|
+
<link rel="modulepreload" href="${initialUrl}" />
|
|
31408
31473
|
</head>
|
|
31409
31474
|
<body>
|
|
31410
31475
|
<div id="root"></div>
|
|
31411
31476
|
<script type="module">
|
|
31412
|
-
import {
|
|
31413
|
-
|
|
31414
|
-
import { PlatformApp } from "@arcote.tech/platform";
|
|
31415
|
-
|
|
31416
|
-
createRoot(document.getElementById("root")).render(
|
|
31417
|
-
createElement(PlatformApp)
|
|
31418
|
-
);
|
|
31477
|
+
import { startApp } from "${initialUrl}";
|
|
31478
|
+
startApp("root");
|
|
31419
31479
|
</script>
|
|
31420
31480
|
</body>
|
|
31421
31481
|
</html>`;
|
|
@@ -31440,28 +31500,39 @@ function getMime(path4) {
|
|
|
31440
31500
|
return MIME[ext2] ?? "application/octet-stream";
|
|
31441
31501
|
}
|
|
31442
31502
|
function serveFile(filePath, headers = {}) {
|
|
31443
|
-
if (!
|
|
31503
|
+
if (!existsSync18(filePath))
|
|
31444
31504
|
return new Response("Not Found", { status: 404 });
|
|
31445
31505
|
return new Response(Bun.file(filePath), {
|
|
31446
31506
|
headers: { "Content-Type": getMime(filePath), ...headers }
|
|
31447
31507
|
});
|
|
31448
31508
|
}
|
|
31449
|
-
var MODULE_SIG_SECRET = process.env.ARC_MODULE_SECRET ??
|
|
31509
|
+
var MODULE_SIG_SECRET = process.env.ARC_MODULE_SECRET ?? "";
|
|
31450
31510
|
var MODULE_SIG_TTL = 3600;
|
|
31451
|
-
function
|
|
31511
|
+
function ensureModuleSigSecret(ws, devMode) {
|
|
31512
|
+
if (MODULE_SIG_SECRET)
|
|
31513
|
+
return;
|
|
31514
|
+
if (devMode) {
|
|
31515
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
31516
|
+
hasher.update(`arc-dev-secret:${ws.rootDir}`);
|
|
31517
|
+
MODULE_SIG_SECRET = hasher.digest("hex").slice(0, 32);
|
|
31518
|
+
} else {
|
|
31519
|
+
MODULE_SIG_SECRET = crypto.randomUUID();
|
|
31520
|
+
}
|
|
31521
|
+
}
|
|
31522
|
+
function signGroupUrl(file) {
|
|
31452
31523
|
const exp = Math.floor(Date.now() / 1000) + MODULE_SIG_TTL;
|
|
31453
31524
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
31454
|
-
hasher.update(`${
|
|
31525
|
+
hasher.update(`${file}:${exp}:${MODULE_SIG_SECRET}`);
|
|
31455
31526
|
const sig = hasher.digest("hex").slice(0, 16);
|
|
31456
|
-
return `/
|
|
31527
|
+
return `/browser/${file}?sig=${sig}&exp=${exp}`;
|
|
31457
31528
|
}
|
|
31458
|
-
function
|
|
31529
|
+
function verifyGroupSignature(file, sig, exp) {
|
|
31459
31530
|
if (!sig || !exp)
|
|
31460
31531
|
return false;
|
|
31461
31532
|
if (Number(exp) < Date.now() / 1000)
|
|
31462
31533
|
return false;
|
|
31463
31534
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
31464
|
-
hasher.update(`${
|
|
31535
|
+
hasher.update(`${file}:${exp}:${MODULE_SIG_SECRET}`);
|
|
31465
31536
|
return hasher.digest("hex").slice(0, 16) === sig;
|
|
31466
31537
|
}
|
|
31467
31538
|
function decodeTokenPayload(jwt2) {
|
|
@@ -31496,52 +31567,67 @@ function parseArcTokensHeader(header) {
|
|
|
31496
31567
|
return payloads;
|
|
31497
31568
|
}
|
|
31498
31569
|
async function filterManifestForTokens(manifest, moduleAccessMap, tokenPayloads) {
|
|
31499
|
-
const
|
|
31500
|
-
for (const
|
|
31501
|
-
|
|
31502
|
-
|
|
31503
|
-
|
|
31570
|
+
const allowedGroups = new Set;
|
|
31571
|
+
for (const t of tokenPayloads) {
|
|
31572
|
+
if (t?.tokenType)
|
|
31573
|
+
allowedGroups.add(t.tokenType);
|
|
31574
|
+
}
|
|
31575
|
+
const filteredGroups = {};
|
|
31576
|
+
for (const [name, group] of Object.entries(manifest.groups)) {
|
|
31577
|
+
if (!allowedGroups.has(name))
|
|
31504
31578
|
continue;
|
|
31505
|
-
|
|
31506
|
-
|
|
31507
|
-
|
|
31508
|
-
|
|
31509
|
-
|
|
31510
|
-
|
|
31511
|
-
|
|
31579
|
+
let allGranted = true;
|
|
31580
|
+
for (const moduleName of group.modules) {
|
|
31581
|
+
const access = moduleAccessMap.get(moduleName);
|
|
31582
|
+
if (!access || access.rules.length === 0)
|
|
31583
|
+
continue;
|
|
31584
|
+
let granted = false;
|
|
31585
|
+
for (const rule of access.rules) {
|
|
31586
|
+
if (rule.token.name !== name)
|
|
31587
|
+
continue;
|
|
31588
|
+
const matching = tokenPayloads.find((t) => t.tokenType === name);
|
|
31589
|
+
if (!matching)
|
|
31590
|
+
continue;
|
|
31512
31591
|
granted = rule.check ? await rule.check(matching) : true;
|
|
31513
31592
|
if (granted)
|
|
31514
31593
|
break;
|
|
31515
31594
|
}
|
|
31595
|
+
if (!granted) {
|
|
31596
|
+
allGranted = false;
|
|
31597
|
+
break;
|
|
31598
|
+
}
|
|
31516
31599
|
}
|
|
31517
|
-
if (
|
|
31518
|
-
|
|
31600
|
+
if (allGranted) {
|
|
31601
|
+
filteredGroups[name] = { ...group, url: signGroupUrl(group.file) };
|
|
31519
31602
|
}
|
|
31520
31603
|
}
|
|
31521
31604
|
return {
|
|
31522
|
-
|
|
31523
|
-
|
|
31605
|
+
initial: manifest.initial,
|
|
31606
|
+
groups: filteredGroups,
|
|
31607
|
+
sharedChunks: manifest.sharedChunks,
|
|
31524
31608
|
stylesHash: manifest.stylesHash,
|
|
31525
31609
|
buildTime: manifest.buildTime
|
|
31526
31610
|
};
|
|
31527
31611
|
}
|
|
31528
|
-
|
|
31612
|
+
var BROWSER_FILE_RE = /^[A-Za-z0-9_.-]+\.js$/;
|
|
31613
|
+
function staticFilesHandler(ws, devMode, getManifest) {
|
|
31529
31614
|
return (_req, url, ctx) => {
|
|
31530
31615
|
const path4 = url.pathname;
|
|
31531
|
-
if (path4.startsWith("/
|
|
31532
|
-
|
|
31533
|
-
|
|
31534
|
-
|
|
31535
|
-
|
|
31536
|
-
const
|
|
31537
|
-
|
|
31616
|
+
if (path4.startsWith("/browser/")) {
|
|
31617
|
+
const file = path4.slice(9);
|
|
31618
|
+
if (!BROWSER_FILE_RE.test(file)) {
|
|
31619
|
+
return new Response("Not Found", { status: 404, headers: ctx.corsHeaders });
|
|
31620
|
+
}
|
|
31621
|
+
const manifest = getManifest();
|
|
31622
|
+
const isGroupEntry = Object.values(manifest.groups).some((g3) => g3.file === file);
|
|
31623
|
+
if (isGroupEntry) {
|
|
31538
31624
|
const sig = url.searchParams.get("sig");
|
|
31539
31625
|
const exp = url.searchParams.get("exp");
|
|
31540
|
-
if (!
|
|
31626
|
+
if (!verifyGroupSignature(file, sig, exp)) {
|
|
31541
31627
|
return new Response("Forbidden", { status: 403, headers: ctx.corsHeaders });
|
|
31542
31628
|
}
|
|
31543
31629
|
}
|
|
31544
|
-
return serveFile(join20(ws.
|
|
31630
|
+
return serveFile(join20(ws.browserDir, file), {
|
|
31545
31631
|
...ctx.corsHeaders,
|
|
31546
31632
|
"Cache-Control": devMode ? "no-cache" : "max-age=31536000,immutable"
|
|
31547
31633
|
});
|
|
@@ -31559,7 +31645,7 @@ function staticFilesHandler(ws, devMode, moduleAccessMap) {
|
|
|
31559
31645
|
}
|
|
31560
31646
|
if (path4.lastIndexOf(".") > path4.lastIndexOf("/")) {
|
|
31561
31647
|
const publicFile = join20(ws.publicDir, path4.slice(1));
|
|
31562
|
-
if (
|
|
31648
|
+
if (existsSync18(publicFile))
|
|
31563
31649
|
return serveFile(publicFile, ctx.corsHeaders);
|
|
31564
31650
|
}
|
|
31565
31651
|
return null;
|
|
@@ -31584,7 +31670,7 @@ function apiEndpointsHandler(ws, getManifest, cm, moduleAccessMap) {
|
|
|
31584
31670
|
if (url.pathname === "/health") {
|
|
31585
31671
|
return Response.json({
|
|
31586
31672
|
status: "ok",
|
|
31587
|
-
|
|
31673
|
+
groups: Object.keys(getManifest().groups).length,
|
|
31588
31674
|
clients: cm?.clientCount ?? 0
|
|
31589
31675
|
}, { headers: ctx.corsHeaders });
|
|
31590
31676
|
}
|
|
@@ -31616,22 +31702,23 @@ function devReloadHandler(sseClients) {
|
|
|
31616
31702
|
});
|
|
31617
31703
|
};
|
|
31618
31704
|
}
|
|
31619
|
-
function spaFallbackHandler(
|
|
31705
|
+
function spaFallbackHandler(getShellHtml) {
|
|
31620
31706
|
return (_req, _url, ctx) => {
|
|
31621
|
-
return new Response(
|
|
31707
|
+
return new Response(getShellHtml(), {
|
|
31622
31708
|
headers: { ...ctx.corsHeaders, "Content-Type": "text/html" }
|
|
31623
31709
|
});
|
|
31624
31710
|
};
|
|
31625
31711
|
}
|
|
31626
31712
|
async function startPlatformServer(opts) {
|
|
31627
31713
|
const { ws, port, devMode, context } = opts;
|
|
31714
|
+
ensureModuleSigSecret(ws, !!devMode);
|
|
31628
31715
|
const moduleAccessMap = opts.moduleAccess ?? new Map;
|
|
31629
31716
|
let manifest = opts.manifest;
|
|
31630
31717
|
const getManifest = () => manifest;
|
|
31631
31718
|
const setManifest = (m4) => {
|
|
31632
31719
|
manifest = m4;
|
|
31633
31720
|
};
|
|
31634
|
-
const
|
|
31721
|
+
const getShellHtml = () => generateShellHtml(ws.appName, ws.manifest, manifest?.initial);
|
|
31635
31722
|
const sseClients = new Set;
|
|
31636
31723
|
const notifyReload = (m4) => {
|
|
31637
31724
|
const data = JSON.stringify(m4);
|
|
@@ -31645,13 +31732,6 @@ async function startPlatformServer(opts) {
|
|
|
31645
31732
|
}
|
|
31646
31733
|
}
|
|
31647
31734
|
};
|
|
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
31735
|
if (!context) {
|
|
31656
31736
|
const cors = {
|
|
31657
31737
|
"Access-Control-Allow-Origin": "*",
|
|
@@ -31673,11 +31753,10 @@ async function startPlatformServer(opts) {
|
|
|
31673
31753
|
corsHeaders: cors
|
|
31674
31754
|
};
|
|
31675
31755
|
const handlers = [
|
|
31676
|
-
...deployApiHandler ? [deployApiHandler] : [],
|
|
31677
31756
|
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
|
|
31678
31757
|
devReloadHandler(sseClients),
|
|
31679
|
-
staticFilesHandler(ws, !!devMode,
|
|
31680
|
-
spaFallbackHandler(
|
|
31758
|
+
staticFilesHandler(ws, !!devMode, getManifest),
|
|
31759
|
+
spaFallbackHandler(getShellHtml)
|
|
31681
31760
|
];
|
|
31682
31761
|
for (const handler of handlers) {
|
|
31683
31762
|
const response = await handler(req, url, ctx);
|
|
@@ -31700,17 +31779,16 @@ async function startPlatformServer(opts) {
|
|
|
31700
31779
|
const dbPath = opts.dbPath || join20(ws.arcDir, "data", "arc.db");
|
|
31701
31780
|
const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
31702
31781
|
if (dbDir)
|
|
31703
|
-
|
|
31782
|
+
mkdirSync14(dbDir, { recursive: true });
|
|
31704
31783
|
const arcServer = await createArcServer({
|
|
31705
31784
|
context,
|
|
31706
31785
|
dbAdapterFactory: createBunSQLiteAdapterFactory2(dbPath),
|
|
31707
31786
|
port,
|
|
31708
31787
|
httpHandlers: [
|
|
31709
|
-
...deployApiHandler ? [deployApiHandler] : [],
|
|
31710
31788
|
apiEndpointsHandler(ws, getManifest, null, moduleAccessMap),
|
|
31711
31789
|
devReloadHandler(sseClients),
|
|
31712
|
-
staticFilesHandler(ws, !!devMode,
|
|
31713
|
-
spaFallbackHandler(
|
|
31790
|
+
staticFilesHandler(ws, !!devMode, getManifest),
|
|
31791
|
+
spaFallbackHandler(getShellHtml)
|
|
31714
31792
|
],
|
|
31715
31793
|
onWsClose: (clientId) => cleanupClientSubs(clientId)
|
|
31716
31794
|
});
|
|
@@ -31724,32 +31802,53 @@ async function startPlatformServer(opts) {
|
|
|
31724
31802
|
};
|
|
31725
31803
|
}
|
|
31726
31804
|
|
|
31727
|
-
// src/
|
|
31728
|
-
async function
|
|
31729
|
-
const ws =
|
|
31730
|
-
const port = 5005;
|
|
31731
|
-
|
|
31805
|
+
// src/platform/startup.ts
|
|
31806
|
+
async function startPlatform(opts) {
|
|
31807
|
+
const { ws, devMode } = opts;
|
|
31808
|
+
const port = opts.port ?? parseInt(process.env.PORT || "5005", 10);
|
|
31809
|
+
const dbPath = opts.dbPath ?? join21(ws.rootDir, ".arc", "data", devMode ? "dev.db" : "prod.db");
|
|
31810
|
+
let manifest;
|
|
31811
|
+
if (devMode) {
|
|
31812
|
+
manifest = await buildAll(ws);
|
|
31813
|
+
} else {
|
|
31814
|
+
const manifestPath = join21(ws.arcDir, "manifest.json");
|
|
31815
|
+
if (!existsSync19(manifestPath)) {
|
|
31816
|
+
err("No build found. Run `arc platform build` first.");
|
|
31817
|
+
process.exit(1);
|
|
31818
|
+
}
|
|
31819
|
+
manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
|
|
31820
|
+
}
|
|
31732
31821
|
log2("Loading server context...");
|
|
31733
|
-
const { context, moduleAccess } = await loadServerContext(ws
|
|
31822
|
+
const { context, moduleAccess } = await loadServerContext(ws);
|
|
31734
31823
|
if (context) {
|
|
31735
31824
|
ok("Context loaded");
|
|
31736
31825
|
} else {
|
|
31737
31826
|
log2("No context \u2014 server endpoints skipped");
|
|
31738
31827
|
}
|
|
31739
|
-
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
31740
31828
|
const platform3 = await startPlatformServer({
|
|
31741
31829
|
ws,
|
|
31742
31830
|
port,
|
|
31743
31831
|
manifest,
|
|
31744
31832
|
context,
|
|
31745
31833
|
moduleAccess,
|
|
31746
|
-
dbPath
|
|
31747
|
-
devMode
|
|
31748
|
-
arcEntries
|
|
31834
|
+
dbPath,
|
|
31835
|
+
devMode
|
|
31749
31836
|
});
|
|
31750
31837
|
ok(`Server on http://localhost:${port}`);
|
|
31751
|
-
if (platform3.contextHandler)
|
|
31838
|
+
if (platform3.contextHandler) {
|
|
31752
31839
|
ok("Commands, queries, WebSocket \u2014 all on same port");
|
|
31840
|
+
}
|
|
31841
|
+
if (devMode) {
|
|
31842
|
+
attachDevWatcher(ws, platform3);
|
|
31843
|
+
}
|
|
31844
|
+
const cleanup = () => {
|
|
31845
|
+
platform3.stop();
|
|
31846
|
+
process.exit(0);
|
|
31847
|
+
};
|
|
31848
|
+
process.on("SIGTERM", cleanup);
|
|
31849
|
+
process.on("SIGINT", cleanup);
|
|
31850
|
+
}
|
|
31851
|
+
function attachDevWatcher(ws, platform3) {
|
|
31753
31852
|
log2("Watching for changes...");
|
|
31754
31853
|
let rebuildTimer = null;
|
|
31755
31854
|
let isRebuilding = false;
|
|
@@ -31762,10 +31861,10 @@ async function platformDev(opts = {}) {
|
|
|
31762
31861
|
isRebuilding = true;
|
|
31763
31862
|
log2("Rebuilding...");
|
|
31764
31863
|
try {
|
|
31765
|
-
|
|
31766
|
-
platform3.setManifest(
|
|
31767
|
-
platform3.notifyReload(
|
|
31768
|
-
ok(`Rebuilt \u2014 ${
|
|
31864
|
+
const next = await buildAll(ws);
|
|
31865
|
+
platform3.setManifest(next);
|
|
31866
|
+
platform3.notifyReload(next);
|
|
31867
|
+
ok(`Rebuilt \u2014 initial + ${Object.keys(next.groups).length} group(s)`);
|
|
31769
31868
|
} catch (e2) {
|
|
31770
31869
|
console.error(`Rebuild failed: ${e2}`);
|
|
31771
31870
|
} finally {
|
|
@@ -31775,7 +31874,7 @@ async function platformDev(opts = {}) {
|
|
|
31775
31874
|
};
|
|
31776
31875
|
for (const pkg of ws.packages) {
|
|
31777
31876
|
const srcDir = join21(pkg.path, "src");
|
|
31778
|
-
if (!
|
|
31877
|
+
if (!existsSync19(srcDir))
|
|
31779
31878
|
continue;
|
|
31780
31879
|
watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
31781
31880
|
if (!filename || filename.includes(".arc") || filename.endsWith(".d.ts") || filename.includes("node_modules") || filename.includes("dist"))
|
|
@@ -31786,90 +31885,25 @@ async function platformDev(opts = {}) {
|
|
|
31786
31885
|
});
|
|
31787
31886
|
}
|
|
31788
31887
|
const localesDir = join21(ws.rootDir, "locales");
|
|
31789
|
-
if (
|
|
31888
|
+
if (existsSync19(localesDir)) {
|
|
31790
31889
|
watch(localesDir, { recursive: false }, (_event, filename) => {
|
|
31791
31890
|
if (!filename?.endsWith(".po"))
|
|
31792
31891
|
return;
|
|
31793
31892
|
triggerRebuild();
|
|
31794
31893
|
});
|
|
31795
31894
|
}
|
|
31796
|
-
|
|
31797
|
-
|
|
31798
|
-
|
|
31799
|
-
|
|
31800
|
-
|
|
31801
|
-
|
|
31895
|
+
}
|
|
31896
|
+
|
|
31897
|
+
// src/commands/platform-dev.ts
|
|
31898
|
+
async function platformDev(opts = {}) {
|
|
31899
|
+
const ws = resolveWorkspace();
|
|
31900
|
+
await startPlatform({ ws, devMode: true });
|
|
31802
31901
|
}
|
|
31803
31902
|
|
|
31804
31903
|
// src/commands/platform-start.ts
|
|
31805
|
-
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
31806
|
-
import { join as join22 } from "path";
|
|
31807
31904
|
async function platformStart() {
|
|
31808
31905
|
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);
|
|
31906
|
+
await startPlatform({ ws, devMode: false });
|
|
31873
31907
|
}
|
|
31874
31908
|
|
|
31875
31909
|
// src/index.ts
|
|
@@ -31881,8 +31915,7 @@ var platform3 = program2.command("platform").description("Platform commands \u20
|
|
|
31881
31915
|
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
31916
|
platform3.command("build").description("Build platform for production").option("--no-cache", "Force full rebuild").action((opts) => platformBuild({ noCache: opts.cache === false }));
|
|
31883
31917
|
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));
|
|
31918
|
+
platform3.command("deploy [env]").description("Deploy platform to a remote server (reads deploy.arc.json, surveys if missing)").option("--skip-build", "Skip local build step").option("--rebuild", "Force rebuild before deploy").option("--build-only", "Build the Docker image locally, then exit (no remote push)").option("--image-tag <hash>", "Roll back / pin to an existing image tag instead of building a new one").option("--force-bootstrap", "Re-run Ansible host bootstrap even if the server is already configured").action((env2, opts) => platformDeploy(env2, opts));
|
|
31886
31919
|
program2.parse(process.argv);
|
|
31887
31920
|
if (process.argv.length === 2) {
|
|
31888
31921
|
program2.help();
|