@arcote.tech/arc-cli 0.5.6 → 0.5.8
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 +768 -401
- package/package.json +7 -7
- package/src/builder/build-cache.ts +79 -0
- package/src/builder/hash.ts +100 -0
- package/src/builder/module-builder.ts +393 -197
- package/src/builder/parallel.ts +26 -0
- package/src/commands/platform-build.ts +2 -2
- package/src/commands/platform-deploy.ts +1 -1
- package/src/commands/platform-dev.ts +28 -45
- package/src/index.ts +8 -2
- package/src/platform/server.ts +10 -0
- package/src/platform/shared.ts +349 -143
package/src/platform/shared.ts
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import { findUpSync } from "find-up";
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
buildContextPackages,
|
|
6
|
+
buildModulesBundle,
|
|
6
7
|
buildStyles,
|
|
8
|
+
buildTranslations,
|
|
7
9
|
discoverPackages,
|
|
8
10
|
isContextPackage,
|
|
9
|
-
sha256OfFiles,
|
|
10
11
|
type BuildManifest,
|
|
11
12
|
type ModuleDescriptor,
|
|
12
|
-
type ModuleEntry,
|
|
13
13
|
type WorkspacePackage,
|
|
14
14
|
} from "../builder/module-builder";
|
|
15
|
+
import {
|
|
16
|
+
loadBuildCache,
|
|
17
|
+
saveBuildCache,
|
|
18
|
+
isCacheHit,
|
|
19
|
+
updateCache,
|
|
20
|
+
type BuildCache,
|
|
21
|
+
} from "../builder/build-cache";
|
|
22
|
+
import {
|
|
23
|
+
mtimeOf,
|
|
24
|
+
readInstalledVersion,
|
|
25
|
+
sha256Hex,
|
|
26
|
+
sha256OfDir,
|
|
27
|
+
sha256OfFiles,
|
|
28
|
+
sha256OfJson,
|
|
29
|
+
} from "../builder/hash";
|
|
30
|
+
import { pAll } from "../builder/parallel";
|
|
15
31
|
|
|
16
32
|
// Re-export for convenience
|
|
17
|
-
export {
|
|
18
|
-
export type { BuildManifest, ModuleDescriptor,
|
|
33
|
+
export { buildContextPackages, buildModulesBundle, buildStyles, isContextPackage };
|
|
34
|
+
export type { BuildManifest, ModuleDescriptor, WorkspacePackage };
|
|
19
35
|
|
|
20
36
|
// ---------------------------------------------------------------------------
|
|
21
37
|
// Logging
|
|
@@ -51,6 +67,8 @@ export interface WorkspaceInfo {
|
|
|
51
67
|
arcDir: string;
|
|
52
68
|
modulesDir: string;
|
|
53
69
|
shellDir: string;
|
|
70
|
+
/** Static assets generated z arc.browserAssets workspace deps. Serwowane pod /assets/*. */
|
|
71
|
+
assetsDir: string;
|
|
54
72
|
publicDir: string;
|
|
55
73
|
packages: WorkspacePackage[];
|
|
56
74
|
manifest?: ManifestInfo;
|
|
@@ -107,6 +125,7 @@ export function resolveWorkspace(): WorkspaceInfo {
|
|
|
107
125
|
arcDir,
|
|
108
126
|
modulesDir: join(arcDir, "modules"),
|
|
109
127
|
shellDir: join(arcDir, "shell"),
|
|
128
|
+
assetsDir: join(arcDir, "assets"),
|
|
110
129
|
publicDir: join(rootDir, "public"),
|
|
111
130
|
packages,
|
|
112
131
|
manifest,
|
|
@@ -117,32 +136,39 @@ export function resolveWorkspace(): WorkspaceInfo {
|
|
|
117
136
|
// Build pipeline — packages, CSS, theme, shell
|
|
118
137
|
// ---------------------------------------------------------------------------
|
|
119
138
|
|
|
120
|
-
export
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
ok(`Built ${manifest.modules.length} module(s)`);
|
|
124
|
-
|
|
125
|
-
log("Building styles...");
|
|
126
|
-
await buildStyles(ws.rootDir, ws.arcDir);
|
|
127
|
-
ok("Styles built");
|
|
128
|
-
|
|
129
|
-
// Copy theme if configured
|
|
130
|
-
const themePath = ws.rootPkg.arc?.theme;
|
|
131
|
-
if (themePath) {
|
|
132
|
-
const src = join(ws.rootDir, themePath);
|
|
133
|
-
if (existsSync(src)) {
|
|
134
|
-
copyFileSync(src, join(ws.arcDir, "theme.css"));
|
|
135
|
-
ok("Theme copied");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
log("Building shell...");
|
|
140
|
-
await buildShell(ws.shellDir, ws.packages);
|
|
141
|
-
ok("Shell built");
|
|
139
|
+
export interface BuildOptions {
|
|
140
|
+
noCache?: boolean;
|
|
141
|
+
}
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Run the full build pipeline. All units run in parallel; each consults the
|
|
145
|
+
* source-hash cache and skips if its inputs are unchanged. Pass `noCache: true`
|
|
146
|
+
* to force a full rebuild (the cache is still updated).
|
|
147
|
+
*/
|
|
148
|
+
export async function buildAll(
|
|
149
|
+
ws: WorkspaceInfo,
|
|
150
|
+
opts: BuildOptions = {},
|
|
151
|
+
): Promise<BuildManifest> {
|
|
152
|
+
const cache = loadBuildCache(ws.arcDir);
|
|
153
|
+
const noCache = opts.noCache ?? false;
|
|
154
|
+
const themePath = ws.rootPkg.arc?.theme as string | undefined;
|
|
155
|
+
|
|
156
|
+
log(`Building (concurrency parallel${noCache ? ", no-cache" : ""})...`);
|
|
157
|
+
|
|
158
|
+
// Promise.all over independent units. buildModulesBundle is the only one
|
|
159
|
+
// that returns data we need (the manifest). The rest update the cache.
|
|
160
|
+
const [, modulesResult] = await Promise.all([
|
|
161
|
+
buildContextPackages(ws.rootDir, ws.packages, cache, noCache),
|
|
162
|
+
buildModulesBundle(ws.rootDir, ws.modulesDir, ws.packages, cache, noCache),
|
|
163
|
+
buildShell(ws, cache, noCache),
|
|
164
|
+
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
165
|
+
copyBrowserAssets(ws, cache, noCache),
|
|
166
|
+
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
saveBuildCache(ws.arcDir, cache);
|
|
170
|
+
|
|
171
|
+
const finalManifest = assembleManifest(ws, modulesResult.modules, cache);
|
|
146
172
|
writeFileSync(
|
|
147
173
|
join(ws.modulesDir, "manifest.json"),
|
|
148
174
|
JSON.stringify(finalManifest, null, 2),
|
|
@@ -151,38 +177,158 @@ export async function buildAll(ws: WorkspaceInfo): Promise<BuildManifest> {
|
|
|
151
177
|
}
|
|
152
178
|
|
|
153
179
|
/**
|
|
154
|
-
*
|
|
155
|
-
* Pure function — does not touch disk.
|
|
180
|
+
* Assemble the platform manifest from cached output hashes — no disk reads.
|
|
156
181
|
*/
|
|
157
|
-
|
|
182
|
+
function assembleManifest(
|
|
158
183
|
ws: WorkspaceInfo,
|
|
159
|
-
|
|
184
|
+
modules: ModuleDescriptor[],
|
|
185
|
+
cache: BuildCache,
|
|
160
186
|
): BuildManifest {
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
187
|
+
// Aggregate shellHash from all shell:* unit output hashes.
|
|
188
|
+
const shellEntries: Record<string, string> = {};
|
|
189
|
+
for (const [unitId, entry] of Object.entries(cache.units)) {
|
|
190
|
+
if (unitId.startsWith("shell:") && entry.outputHash) {
|
|
191
|
+
shellEntries[unitId] = entry.outputHash;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const shellHash = sha256OfJson(shellEntries);
|
|
195
|
+
const stylesHash = cache.units["styles"]?.outputHash ?? "";
|
|
166
196
|
|
|
167
197
|
return {
|
|
168
|
-
modules
|
|
169
|
-
shellHash
|
|
170
|
-
stylesHash
|
|
171
|
-
buildTime:
|
|
198
|
+
modules,
|
|
199
|
+
shellHash,
|
|
200
|
+
stylesHash,
|
|
201
|
+
buildTime: new Date().toISOString(),
|
|
172
202
|
};
|
|
173
203
|
}
|
|
174
204
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Browser assets — @arcote.tech/* deps deklarują w `arc.browserAssets` jakie
|
|
207
|
+
// pliki muszą być dostępne w przeglądarce (np. SQLite WASM worker + .wasm).
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
interface BrowserAsset {
|
|
211
|
+
/** Path to source file — relative to package dir or package-resolvable. */
|
|
212
|
+
from: string;
|
|
213
|
+
/** Target filename in /assets/. */
|
|
214
|
+
to: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface ResolvedAsset {
|
|
218
|
+
arcPkg: string;
|
|
219
|
+
from: string;
|
|
220
|
+
to: string;
|
|
221
|
+
src: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function resolveAssetSource(
|
|
225
|
+
from: string,
|
|
226
|
+
pkgDir: string,
|
|
227
|
+
rootDir: string,
|
|
228
|
+
): string | null {
|
|
229
|
+
if (from.startsWith("./") || from.startsWith("../")) {
|
|
230
|
+
const resolved = join(pkgDir, from);
|
|
231
|
+
return existsSync(resolved) ? resolved : null;
|
|
232
|
+
}
|
|
233
|
+
const candidates = [
|
|
234
|
+
join(rootDir, "node_modules", from),
|
|
235
|
+
join(pkgDir, "node_modules", from),
|
|
236
|
+
];
|
|
237
|
+
for (const c of candidates) {
|
|
238
|
+
if (existsSync(c)) return c;
|
|
239
|
+
}
|
|
240
|
+
const bunCacheDir = join(rootDir, "node_modules", ".bun");
|
|
241
|
+
if (existsSync(bunCacheDir)) {
|
|
242
|
+
for (const entry of readdirSync(bunCacheDir, { withFileTypes: true })) {
|
|
243
|
+
if (!entry.isDirectory()) continue;
|
|
244
|
+
const candidate = join(
|
|
245
|
+
bunCacheDir,
|
|
246
|
+
entry.name,
|
|
247
|
+
"node_modules",
|
|
248
|
+
from,
|
|
249
|
+
);
|
|
250
|
+
if (existsSync(candidate)) return candidate;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function readBrowserAssets(pkgDir: string): BrowserAsset[] {
|
|
257
|
+
const pkgJsonPath = join(pkgDir, "package.json");
|
|
258
|
+
if (!existsSync(pkgJsonPath)) return [];
|
|
259
|
+
try {
|
|
260
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
261
|
+
const assets = pkg.arc?.browserAssets;
|
|
262
|
+
if (!Array.isArray(assets)) return [];
|
|
263
|
+
return assets.filter(
|
|
264
|
+
(a): a is BrowserAsset =>
|
|
265
|
+
typeof a?.from === "string" && typeof a?.to === "string",
|
|
266
|
+
);
|
|
267
|
+
} catch {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function discoverBrowserAssets(ws: WorkspaceInfo): ResolvedAsset[] {
|
|
273
|
+
const arcDir = join(ws.rootDir, "node_modules", "@arcote.tech");
|
|
274
|
+
if (!existsSync(arcDir)) return [];
|
|
275
|
+
|
|
276
|
+
const out: ResolvedAsset[] = [];
|
|
277
|
+
for (const entry of readdirSync(arcDir, { withFileTypes: true })) {
|
|
278
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
279
|
+
const pkgDir = join(arcDir, entry.name);
|
|
280
|
+
const assets = readBrowserAssets(pkgDir);
|
|
281
|
+
for (const asset of assets) {
|
|
282
|
+
const src = resolveAssetSource(asset.from, pkgDir, ws.rootDir);
|
|
283
|
+
if (!src) {
|
|
284
|
+
err(`browserAsset not found: ${asset.from} (from @arcote.tech/${entry.name})`);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
out.push({ arcPkg: entry.name, from: asset.from, to: asset.to, src });
|
|
288
|
+
}
|
|
182
289
|
}
|
|
183
290
|
return out;
|
|
184
291
|
}
|
|
185
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Copy browser assets as a single cached unit. Hash inputs: every (from, to, src-mtime).
|
|
295
|
+
*/
|
|
296
|
+
async function copyBrowserAssets(
|
|
297
|
+
ws: WorkspaceInfo,
|
|
298
|
+
cache: BuildCache,
|
|
299
|
+
noCache: boolean,
|
|
300
|
+
): Promise<void> {
|
|
301
|
+
mkdirSync(ws.assetsDir, { recursive: true });
|
|
302
|
+
const assets = discoverBrowserAssets(ws);
|
|
303
|
+
if (assets.length === 0) return;
|
|
304
|
+
|
|
305
|
+
const unitId = "browser-assets";
|
|
306
|
+
const inputHash = sha256OfJson(
|
|
307
|
+
assets.map((a) => ({
|
|
308
|
+
arcPkg: a.arcPkg,
|
|
309
|
+
from: a.from,
|
|
310
|
+
to: a.to,
|
|
311
|
+
mtime: mtimeOf(a.src),
|
|
312
|
+
})),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const requiredOutputs = assets.map((a) => join(ws.assetsDir, a.to));
|
|
316
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
317
|
+
console.log(` ✓ cached: browser-assets (${assets.length})`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
console.log(` building: browser-assets (${assets.length})`);
|
|
322
|
+
const outputHashes: Record<string, string> = {};
|
|
323
|
+
for (const asset of assets) {
|
|
324
|
+
const dest = join(ws.assetsDir, asset.to);
|
|
325
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
326
|
+
copyFileSync(asset.src, dest);
|
|
327
|
+
outputHashes[asset.to] = sha256Hex(readFileSync(dest));
|
|
328
|
+
}
|
|
329
|
+
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
330
|
+
}
|
|
331
|
+
|
|
186
332
|
// ---------------------------------------------------------------------------
|
|
187
333
|
// Shell builder — framework packages for import map
|
|
188
334
|
// ---------------------------------------------------------------------------
|
|
@@ -190,18 +336,15 @@ function listFilesRec(dir: string): string[] {
|
|
|
190
336
|
/** Collect all @arcote.tech/* peerDependencies from workspace packages. */
|
|
191
337
|
export function collectArcPeerDeps(packages: WorkspacePackage[]): [string, string][] {
|
|
192
338
|
const seen = new Set<string>();
|
|
193
|
-
// Always include core framework packages
|
|
194
339
|
for (const pkg of ["@arcote.tech/arc", "@arcote.tech/arc-ds", "@arcote.tech/arc-react", "@arcote.tech/platform"]) {
|
|
195
340
|
seen.add(pkg);
|
|
196
341
|
}
|
|
197
|
-
// Scan all workspace packages for @arcote.tech/* peerDeps
|
|
198
342
|
for (const wp of packages) {
|
|
199
343
|
const peerDeps = wp.packageJson.peerDependencies ?? {};
|
|
200
344
|
for (const dep of Object.keys(peerDeps)) {
|
|
201
345
|
if (dep.startsWith("@arcote.tech/")) seen.add(dep);
|
|
202
346
|
}
|
|
203
347
|
}
|
|
204
|
-
// Convert to [shortName, fullName] entries
|
|
205
348
|
return [...seen].map((pkg) => {
|
|
206
349
|
const short = pkg === "@arcote.tech/platform"
|
|
207
350
|
? "platform"
|
|
@@ -210,16 +353,10 @@ export function collectArcPeerDeps(packages: WorkspacePackage[]): [string, strin
|
|
|
210
353
|
});
|
|
211
354
|
}
|
|
212
355
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Step 1: Build React layer (bundles React CJS → ESM)
|
|
219
|
-
const reactEntries: [string, string][] = [
|
|
220
|
-
[
|
|
221
|
-
"react",
|
|
222
|
-
`import React from "react";
|
|
356
|
+
const REACT_ENTRIES: [string, string][] = [
|
|
357
|
+
[
|
|
358
|
+
"react",
|
|
359
|
+
`import React from "react";
|
|
223
360
|
export default React;
|
|
224
361
|
export const {
|
|
225
362
|
Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
|
|
@@ -229,96 +366,172 @@ export const {
|
|
|
229
366
|
useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
|
|
230
367
|
useTransition, version, useActionState, useOptimistic,
|
|
231
368
|
} = React;`,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
369
|
+
],
|
|
370
|
+
["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
|
|
371
|
+
[
|
|
372
|
+
"jsx-dev-runtime",
|
|
373
|
+
`export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`,
|
|
374
|
+
],
|
|
375
|
+
[
|
|
376
|
+
"react-dom",
|
|
377
|
+
`import ReactDOM from "react-dom";
|
|
241
378
|
export default ReactDOM;
|
|
242
379
|
export const { createPortal, flushSync } = ReactDOM;`,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
380
|
+
],
|
|
381
|
+
[
|
|
382
|
+
"react-dom-client",
|
|
383
|
+
`export { createRoot, hydrateRoot } from "react-dom/client";`,
|
|
384
|
+
],
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
const REACT_OUTPUT_FILES = REACT_ENTRIES.map(([n]) => `${n}.js`);
|
|
388
|
+
|
|
389
|
+
const SHELL_BASE_EXTERNAL = [
|
|
390
|
+
"react",
|
|
391
|
+
"react-dom",
|
|
392
|
+
"react/jsx-runtime",
|
|
393
|
+
"react/jsx-dev-runtime",
|
|
394
|
+
"react-dom/client",
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
const sourceFilter = (rel: string): boolean => {
|
|
398
|
+
if (rel.startsWith("dist/") || rel.startsWith("dist")) return false;
|
|
399
|
+
if (rel.includes("/node_modules/") || rel.startsWith("node_modules")) return false;
|
|
400
|
+
if (rel.startsWith(".arc/") || rel.startsWith(".arc")) return false;
|
|
401
|
+
return true;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
function arcPkgSrcHash(rootDir: string, pkg: string): string {
|
|
405
|
+
// Prefer src/ tree (workspace links), fallback to whole package dir.
|
|
406
|
+
const srcDir = join(rootDir, "node_modules", pkg, "src");
|
|
407
|
+
if (existsSync(srcDir)) return sha256OfDir(srcDir, sourceFilter);
|
|
408
|
+
return sha256OfDir(join(rootDir, "node_modules", pkg), sourceFilter);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function buildShellReact(
|
|
412
|
+
shellDir: string,
|
|
413
|
+
tmpDir: string,
|
|
414
|
+
rootDir: string,
|
|
415
|
+
cache: BuildCache,
|
|
416
|
+
noCache: boolean,
|
|
417
|
+
): Promise<void> {
|
|
418
|
+
const unitId = "shell:react";
|
|
419
|
+
const inputHash = sha256OfJson({
|
|
420
|
+
react: readInstalledVersion(rootDir, "react"),
|
|
421
|
+
"react-dom": readInstalledVersion(rootDir, "react-dom"),
|
|
422
|
+
entries: REACT_ENTRIES.map(([k, v]) => [k, v]),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join(shellDir, f));
|
|
426
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
427
|
+
console.log(` ✓ cached: shell:react`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log(` building: shell:react`);
|
|
249
432
|
|
|
250
433
|
const reactEps: string[] = [];
|
|
251
|
-
for (const [name, code] of
|
|
434
|
+
for (const [name, code] of REACT_ENTRIES) {
|
|
252
435
|
const f = join(tmpDir, `${name}.ts`);
|
|
253
|
-
Bun.write(f, code);
|
|
436
|
+
await Bun.write(f, code);
|
|
254
437
|
reactEps.push(f);
|
|
255
438
|
}
|
|
256
439
|
|
|
257
|
-
const
|
|
440
|
+
const r = await Bun.build({
|
|
258
441
|
entrypoints: reactEps,
|
|
259
|
-
outdir:
|
|
442
|
+
outdir: shellDir,
|
|
260
443
|
splitting: true,
|
|
261
444
|
format: "esm",
|
|
262
445
|
target: "browser",
|
|
263
446
|
naming: "[name].[ext]",
|
|
264
447
|
});
|
|
265
|
-
if (!
|
|
266
|
-
for (const l of
|
|
448
|
+
if (!r.success) {
|
|
449
|
+
for (const l of r.logs) console.error(l);
|
|
267
450
|
throw new Error("Shell React build failed");
|
|
268
451
|
}
|
|
269
452
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
? collectArcPeerDeps(packages)
|
|
274
|
-
: [
|
|
275
|
-
// Fallback: core packages only (no workspace packages available)
|
|
276
|
-
["arc", "@arcote.tech/arc"],
|
|
277
|
-
["arc-ds", "@arcote.tech/arc-ds"],
|
|
278
|
-
["arc-react", "@arcote.tech/arc-react"],
|
|
279
|
-
["platform", "@arcote.tech/platform"],
|
|
280
|
-
] as [string, string][];
|
|
281
|
-
|
|
282
|
-
const baseExternal = [
|
|
283
|
-
"react",
|
|
284
|
-
"react-dom",
|
|
285
|
-
"react/jsx-runtime",
|
|
286
|
-
"react/jsx-dev-runtime",
|
|
287
|
-
"react-dom/client",
|
|
288
|
-
];
|
|
289
|
-
const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
|
|
453
|
+
const outputHash = sha256OfFiles(requiredOutputs);
|
|
454
|
+
updateCache(cache, unitId, inputHash, { outputHash });
|
|
455
|
+
}
|
|
290
456
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
for (const l of r2.logs) console.error(l);
|
|
316
|
-
throw new Error(`Shell build failed for ${pkg}`);
|
|
317
|
-
}
|
|
457
|
+
async function buildShellArcEntry(
|
|
458
|
+
shortName: string,
|
|
459
|
+
pkg: string,
|
|
460
|
+
allArcPkgs: string[],
|
|
461
|
+
shellDir: string,
|
|
462
|
+
tmpDir: string,
|
|
463
|
+
rootDir: string,
|
|
464
|
+
cache: BuildCache,
|
|
465
|
+
noCache: boolean,
|
|
466
|
+
): Promise<void> {
|
|
467
|
+
const unitId = `shell:arc:${shortName}`;
|
|
468
|
+
const otherExternals = allArcPkgs.filter((p) => p !== pkg);
|
|
469
|
+
const inputHash = sha256OfJson({
|
|
470
|
+
pkg,
|
|
471
|
+
version: readInstalledVersion(rootDir, pkg),
|
|
472
|
+
src: arcPkgSrcHash(rootDir, pkg),
|
|
473
|
+
base: SHELL_BASE_EXTERNAL,
|
|
474
|
+
others: [...otherExternals].sort(),
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const outputFile = join(shellDir, `${shortName}.js`);
|
|
478
|
+
if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
|
|
479
|
+
console.log(` ✓ cached: ${unitId}`);
|
|
480
|
+
return;
|
|
318
481
|
}
|
|
319
482
|
|
|
320
|
-
|
|
321
|
-
|
|
483
|
+
console.log(` building: ${unitId}`);
|
|
484
|
+
|
|
485
|
+
const f = join(tmpDir, `${shortName}.ts`);
|
|
486
|
+
await Bun.write(f, `export * from "${pkg}";\n`);
|
|
487
|
+
|
|
488
|
+
const r = await Bun.build({
|
|
489
|
+
entrypoints: [f],
|
|
490
|
+
outdir: shellDir,
|
|
491
|
+
format: "esm",
|
|
492
|
+
target: "browser",
|
|
493
|
+
naming: "[name].[ext]",
|
|
494
|
+
external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
|
|
495
|
+
define: {
|
|
496
|
+
ONLY_SERVER: "false",
|
|
497
|
+
ONLY_BROWSER: "true",
|
|
498
|
+
ONLY_CLIENT: "true",
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
if (!r.success) {
|
|
502
|
+
for (const l of r.logs) console.error(l);
|
|
503
|
+
throw new Error(`Shell build failed for ${pkg}`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const outputHash = sha256OfFiles([outputFile]);
|
|
507
|
+
updateCache(cache, unitId, inputHash, { outputHash });
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Build the framework shell — react layer + each @arcote.tech/* package as a
|
|
512
|
+
* separate cacheable unit. Tasks run in parallel via pAll.
|
|
513
|
+
*/
|
|
514
|
+
export async function buildShell(
|
|
515
|
+
ws: WorkspaceInfo,
|
|
516
|
+
cache: BuildCache,
|
|
517
|
+
noCache: boolean,
|
|
518
|
+
): Promise<void> {
|
|
519
|
+
mkdirSync(ws.shellDir, { recursive: true });
|
|
520
|
+
const tmpDir = join(ws.shellDir, "_tmp");
|
|
521
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
522
|
+
|
|
523
|
+
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
524
|
+
const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
|
|
525
|
+
|
|
526
|
+
const tasks: Array<() => Promise<void>> = [
|
|
527
|
+
() => buildShellReact(ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
|
|
528
|
+
...arcEntries.map(([short, pkg]) => () =>
|
|
529
|
+
buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
|
|
530
|
+
),
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
await pAll(tasks);
|
|
534
|
+
|
|
322
535
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
323
536
|
}
|
|
324
537
|
|
|
@@ -338,20 +551,14 @@ export async function loadServerContext(
|
|
|
338
551
|
(globalThis as any).ONLY_BROWSER = false;
|
|
339
552
|
(globalThis as any).ONLY_CLIENT = false;
|
|
340
553
|
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
// Resolve platform from the project's node_modules using an absolute path.
|
|
344
|
-
// When CLI is bun-linked, `import("@arcote.tech/platform")` would resolve to
|
|
345
|
-
// the CLI's own copy, creating a separate module instance from what context
|
|
346
|
-
// packages use. Using an absolute path ensures a single shared instance.
|
|
554
|
+
// Resolve platform from the project's node_modules using an absolute path
|
|
555
|
+
// (see comment in original implementation for why).
|
|
347
556
|
const platformDir = join(process.cwd(), "node_modules", "@arcote.tech", "platform");
|
|
348
557
|
const platformPkg = JSON.parse(readFileSync(join(platformDir, "package.json"), "utf-8"));
|
|
349
558
|
const platformEntry = join(platformDir, platformPkg.main ?? "src/index.ts");
|
|
350
559
|
|
|
351
|
-
// Pre-import platform so it's cached with this absolute path
|
|
352
560
|
await import(platformEntry);
|
|
353
561
|
|
|
354
|
-
// Import context packages from server dist (has server-only code paths)
|
|
355
562
|
for (const ctx of ctxPackages) {
|
|
356
563
|
const serverDist = join(ctx.path, "dist", "server", "main", "index.js");
|
|
357
564
|
if (!existsSync(serverDist)) {
|
|
@@ -366,7 +573,6 @@ export async function loadServerContext(
|
|
|
366
573
|
}
|
|
367
574
|
}
|
|
368
575
|
|
|
369
|
-
// Import non-context packages from source to capture module().protectedBy() metadata
|
|
370
576
|
const nonCtxPackages = packages.filter((p) => !isContextPackage(p.packageJson));
|
|
371
577
|
for (const pkg of nonCtxPackages) {
|
|
372
578
|
try {
|