@arcote.tech/arc-cli 0.7.0 → 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 +1382 -1343
- package/package.json +7 -7
- package/src/builder/access-extractor.ts +10 -24
- package/src/builder/module-builder.ts +293 -153
- package/src/commands/platform-build.ts +2 -1
- package/src/commands/platform-deploy.ts +30 -21
- 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 +137 -28
- package/src/deploy/compose.ts +4 -3
- package/src/deploy/config.ts +38 -3
- package/src/deploy/deploy-env.ts +1 -1
- package/src/deploy/env-file.ts +103 -0
- package/src/deploy/image.ts +7 -1
- package/src/deploy/ssh.ts +51 -2
- package/src/index.ts +5 -0
- package/src/platform/server.ts +99 -99
- package/src/platform/shared.ts +28 -240
- package/src/platform/startup.ts +4 -5
package/src/platform/shared.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { findUpSync } from "find-up";
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync,
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
import {
|
|
5
|
+
buildBrowserApp,
|
|
5
6
|
buildContextPackages,
|
|
6
|
-
buildModulesByChunks,
|
|
7
7
|
buildStyles,
|
|
8
8
|
buildTranslations,
|
|
9
9
|
discoverPackages,
|
|
10
10
|
isContextPackage,
|
|
11
|
+
type BrowserAppResult,
|
|
11
12
|
type BuildManifest,
|
|
12
13
|
type ModuleDescriptor,
|
|
13
14
|
type WorkspacePackage,
|
|
@@ -24,16 +25,13 @@ import { planChunks } from "../builder/chunk-planner";
|
|
|
24
25
|
import { collectFrameworkDeps } from "../builder/dependency-collector";
|
|
25
26
|
import {
|
|
26
27
|
mtimeOf,
|
|
27
|
-
readInstalledVersion,
|
|
28
28
|
sha256Hex,
|
|
29
|
-
sha256OfDir,
|
|
30
29
|
sha256OfFiles,
|
|
31
30
|
sha256OfJson,
|
|
32
31
|
} from "../builder/hash";
|
|
33
|
-
import { pAll } from "../builder/parallel";
|
|
34
32
|
|
|
35
33
|
// Re-export for convenience
|
|
36
|
-
export { buildContextPackages,
|
|
34
|
+
export { buildContextPackages, buildStyles, isContextPackage };
|
|
37
35
|
export type { BuildManifest, ModuleDescriptor, WorkspacePackage };
|
|
38
36
|
|
|
39
37
|
// ---------------------------------------------------------------------------
|
|
@@ -68,8 +66,8 @@ export interface WorkspaceInfo {
|
|
|
68
66
|
rootPkg: Record<string, any>;
|
|
69
67
|
appName: string;
|
|
70
68
|
arcDir: string;
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/** Single Bun.build output dir for browser app: `<arcDir>/browser/`. */
|
|
70
|
+
browserDir: string;
|
|
73
71
|
/** Static assets generated z arc.browserAssets workspace deps. Serwowane pod /assets/*. */
|
|
74
72
|
assetsDir: string;
|
|
75
73
|
publicDir: string;
|
|
@@ -128,8 +126,7 @@ export function resolveWorkspace(): WorkspaceInfo {
|
|
|
128
126
|
rootPkg,
|
|
129
127
|
appName,
|
|
130
128
|
arcDir,
|
|
131
|
-
|
|
132
|
-
shellDir: join(arcDir, "shell"),
|
|
129
|
+
browserDir: join(arcDir, "browser"),
|
|
133
130
|
assetsDir: join(arcDir, "assets"),
|
|
134
131
|
publicDir: join(rootDir, "public"),
|
|
135
132
|
packages,
|
|
@@ -191,16 +188,22 @@ export async function buildAll(
|
|
|
191
188
|
.join(", ")}`,
|
|
192
189
|
);
|
|
193
190
|
|
|
194
|
-
// Phase 4 — independent parallel build units.
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
191
|
+
// Phase 4 — independent parallel build units. The browser app is a single
|
|
192
|
+
// Bun.build call covering initial + all token groups (replaces ~25 separate
|
|
193
|
+
// Bun.builds for shell + per-chunk + per-package).
|
|
194
|
+
const i18nCollector = new Map<string, Set<string>>();
|
|
195
|
+
|
|
196
|
+
const [browserResult] = await Promise.all([
|
|
197
|
+
buildBrowserApp(ws.rootDir, ws.browserDir, plan, cache, noCache, i18nCollector),
|
|
199
198
|
buildStyles(ws.rootDir, ws.arcDir, ws.packages, themePath, cache, noCache),
|
|
200
199
|
copyBrowserAssets(ws, cache, noCache),
|
|
201
200
|
buildTranslations(ws.rootDir, ws.arcDir, cache, noCache),
|
|
202
201
|
]);
|
|
203
202
|
|
|
203
|
+
// Finalize i18n catalogs once after all chunks + initial bundle collected.
|
|
204
|
+
const { finalizeTranslations } = await import("../i18n");
|
|
205
|
+
await finalizeTranslations(ws.rootDir, ws.arcDir, i18nCollector);
|
|
206
|
+
|
|
204
207
|
// Phase 5 — framework peer manifest at `<arcDir>/package.json`. Used by
|
|
205
208
|
// the deploy image build (single `bun install` for all peers, one copy
|
|
206
209
|
// shared across module bundles).
|
|
@@ -208,37 +211,28 @@ export async function buildAll(
|
|
|
208
211
|
|
|
209
212
|
saveBuildCache(ws.arcDir, cache);
|
|
210
213
|
|
|
211
|
-
const finalManifest = assembleManifest(ws,
|
|
214
|
+
const finalManifest = assembleManifest(ws, browserResult, cache);
|
|
212
215
|
writeFileSync(
|
|
213
|
-
join(ws.
|
|
216
|
+
join(ws.arcDir, "manifest.json"),
|
|
214
217
|
JSON.stringify(finalManifest, null, 2),
|
|
215
218
|
);
|
|
216
219
|
return finalManifest;
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
/**
|
|
220
|
-
* Assemble the platform manifest from
|
|
223
|
+
* Assemble the platform manifest from the browser app result + style hash.
|
|
221
224
|
*/
|
|
222
225
|
function assembleManifest(
|
|
223
226
|
ws: WorkspaceInfo,
|
|
224
|
-
|
|
225
|
-
chunks: readonly string[],
|
|
227
|
+
browser: BrowserAppResult,
|
|
226
228
|
cache: BuildCache,
|
|
227
229
|
): BuildManifest {
|
|
228
|
-
// Aggregate shellHash from all shell:* unit output hashes.
|
|
229
|
-
const shellEntries: Record<string, string> = {};
|
|
230
|
-
for (const [unitId, entry] of Object.entries(cache.units)) {
|
|
231
|
-
if (unitId.startsWith("shell:") && entry.outputHash) {
|
|
232
|
-
shellEntries[unitId] = entry.outputHash;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const shellHash = sha256OfJson(shellEntries);
|
|
236
230
|
const stylesHash = cache.units["styles"]?.outputHash ?? "";
|
|
237
231
|
|
|
238
232
|
return {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
233
|
+
initial: browser.initial,
|
|
234
|
+
groups: browser.groups,
|
|
235
|
+
sharedChunks: browser.sharedChunks,
|
|
242
236
|
stylesHash,
|
|
243
237
|
buildTime: new Date().toISOString(),
|
|
244
238
|
};
|
|
@@ -399,212 +393,6 @@ async function copyBrowserAssets(
|
|
|
399
393
|
updateCache(cache, unitId, inputHash, { outputHashes });
|
|
400
394
|
}
|
|
401
395
|
|
|
402
|
-
// ---------------------------------------------------------------------------
|
|
403
|
-
// Shell builder — framework packages for import map
|
|
404
|
-
// ---------------------------------------------------------------------------
|
|
405
|
-
|
|
406
|
-
/** Collect all @arcote.tech/* peerDependencies from workspace packages. */
|
|
407
|
-
export function collectArcPeerDeps(packages: WorkspacePackage[]): [string, string][] {
|
|
408
|
-
const seen = new Set<string>();
|
|
409
|
-
for (const pkg of ["@arcote.tech/arc", "@arcote.tech/arc-ds", "@arcote.tech/arc-react", "@arcote.tech/platform"]) {
|
|
410
|
-
seen.add(pkg);
|
|
411
|
-
}
|
|
412
|
-
for (const wp of packages) {
|
|
413
|
-
const peerDeps = wp.packageJson.peerDependencies ?? {};
|
|
414
|
-
for (const dep of Object.keys(peerDeps)) {
|
|
415
|
-
if (dep.startsWith("@arcote.tech/")) seen.add(dep);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return [...seen].map((pkg) => {
|
|
419
|
-
const short = pkg === "@arcote.tech/platform"
|
|
420
|
-
? "platform"
|
|
421
|
-
: pkg.replace("@arcote.tech/", "");
|
|
422
|
-
return [short, pkg];
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const REACT_ENTRIES: [string, string][] = [
|
|
427
|
-
[
|
|
428
|
-
"react",
|
|
429
|
-
`import React from "react";
|
|
430
|
-
export default React;
|
|
431
|
-
export const {
|
|
432
|
-
Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense,
|
|
433
|
-
cloneElement, createContext, createElement, createRef, forwardRef, isValidElement,
|
|
434
|
-
lazy, memo, startTransition, use, useCallback, useContext, useDebugValue,
|
|
435
|
-
useDeferredValue, useEffect, useId, useImperativeHandle, useInsertionEffect,
|
|
436
|
-
useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore,
|
|
437
|
-
useTransition, version, useActionState, useOptimistic,
|
|
438
|
-
} = React;`,
|
|
439
|
-
],
|
|
440
|
-
["jsx-runtime", `export { jsx, jsxs, Fragment } from "react/jsx-runtime";`],
|
|
441
|
-
[
|
|
442
|
-
"jsx-dev-runtime",
|
|
443
|
-
`export { jsxDEV, Fragment } from "react/jsx-dev-runtime";`,
|
|
444
|
-
],
|
|
445
|
-
[
|
|
446
|
-
"react-dom",
|
|
447
|
-
`import ReactDOM from "react-dom";
|
|
448
|
-
export default ReactDOM;
|
|
449
|
-
export const { createPortal, flushSync } = ReactDOM;`,
|
|
450
|
-
],
|
|
451
|
-
[
|
|
452
|
-
"react-dom-client",
|
|
453
|
-
`export { createRoot, hydrateRoot } from "react-dom/client";`,
|
|
454
|
-
],
|
|
455
|
-
];
|
|
456
|
-
|
|
457
|
-
const REACT_OUTPUT_FILES = REACT_ENTRIES.map(([n]) => `${n}.js`);
|
|
458
|
-
|
|
459
|
-
const SHELL_BASE_EXTERNAL = [
|
|
460
|
-
"react",
|
|
461
|
-
"react-dom",
|
|
462
|
-
"react/jsx-runtime",
|
|
463
|
-
"react/jsx-dev-runtime",
|
|
464
|
-
"react-dom/client",
|
|
465
|
-
];
|
|
466
|
-
|
|
467
|
-
const sourceFilter = (rel: string): boolean => {
|
|
468
|
-
if (rel.startsWith("dist/") || rel.startsWith("dist")) return false;
|
|
469
|
-
if (rel.includes("/node_modules/") || rel.startsWith("node_modules")) return false;
|
|
470
|
-
if (rel.startsWith(".arc/") || rel.startsWith(".arc")) return false;
|
|
471
|
-
return true;
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
function arcPkgSrcHash(rootDir: string, pkg: string): string {
|
|
475
|
-
// Prefer src/ tree (workspace links), fallback to whole package dir.
|
|
476
|
-
const srcDir = join(rootDir, "node_modules", pkg, "src");
|
|
477
|
-
if (existsSync(srcDir)) return sha256OfDir(srcDir, sourceFilter);
|
|
478
|
-
return sha256OfDir(join(rootDir, "node_modules", pkg), sourceFilter);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
async function buildShellReact(
|
|
482
|
-
shellDir: string,
|
|
483
|
-
tmpDir: string,
|
|
484
|
-
rootDir: string,
|
|
485
|
-
cache: BuildCache,
|
|
486
|
-
noCache: boolean,
|
|
487
|
-
): Promise<void> {
|
|
488
|
-
const unitId = "shell:react";
|
|
489
|
-
const inputHash = sha256OfJson({
|
|
490
|
-
react: readInstalledVersion(rootDir, "react"),
|
|
491
|
-
"react-dom": readInstalledVersion(rootDir, "react-dom"),
|
|
492
|
-
entries: REACT_ENTRIES.map(([k, v]) => [k, v]),
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
const requiredOutputs = REACT_OUTPUT_FILES.map((f) => join(shellDir, f));
|
|
496
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, requiredOutputs)) {
|
|
497
|
-
console.log(` ✓ cached: shell:react`);
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
console.log(` building: shell:react`);
|
|
502
|
-
|
|
503
|
-
const reactEps: string[] = [];
|
|
504
|
-
for (const [name, code] of REACT_ENTRIES) {
|
|
505
|
-
const f = join(tmpDir, `${name}.ts`);
|
|
506
|
-
await Bun.write(f, code);
|
|
507
|
-
reactEps.push(f);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const r = await Bun.build({
|
|
511
|
-
entrypoints: reactEps,
|
|
512
|
-
outdir: shellDir,
|
|
513
|
-
splitting: true,
|
|
514
|
-
format: "esm",
|
|
515
|
-
target: "browser",
|
|
516
|
-
naming: "[name].[ext]",
|
|
517
|
-
});
|
|
518
|
-
if (!r.success) {
|
|
519
|
-
for (const l of r.logs) console.error(l);
|
|
520
|
-
throw new Error("Shell React build failed");
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const outputHash = sha256OfFiles(requiredOutputs);
|
|
524
|
-
updateCache(cache, unitId, inputHash, { outputHash });
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
async function buildShellArcEntry(
|
|
528
|
-
shortName: string,
|
|
529
|
-
pkg: string,
|
|
530
|
-
allArcPkgs: string[],
|
|
531
|
-
shellDir: string,
|
|
532
|
-
tmpDir: string,
|
|
533
|
-
rootDir: string,
|
|
534
|
-
cache: BuildCache,
|
|
535
|
-
noCache: boolean,
|
|
536
|
-
): Promise<void> {
|
|
537
|
-
const unitId = `shell:arc:${shortName}`;
|
|
538
|
-
const otherExternals = allArcPkgs.filter((p) => p !== pkg);
|
|
539
|
-
const inputHash = sha256OfJson({
|
|
540
|
-
pkg,
|
|
541
|
-
version: readInstalledVersion(rootDir, pkg),
|
|
542
|
-
src: arcPkgSrcHash(rootDir, pkg),
|
|
543
|
-
base: SHELL_BASE_EXTERNAL,
|
|
544
|
-
others: [...otherExternals].sort(),
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
const outputFile = join(shellDir, `${shortName}.js`);
|
|
548
|
-
if (!noCache && isCacheHit(cache, unitId, inputHash, [outputFile])) {
|
|
549
|
-
console.log(` ✓ cached: ${unitId}`);
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
console.log(` building: ${unitId}`);
|
|
554
|
-
|
|
555
|
-
const f = join(tmpDir, `${shortName}.ts`);
|
|
556
|
-
await Bun.write(f, `export * from "${pkg}";\n`);
|
|
557
|
-
|
|
558
|
-
const r = await Bun.build({
|
|
559
|
-
entrypoints: [f],
|
|
560
|
-
outdir: shellDir,
|
|
561
|
-
format: "esm",
|
|
562
|
-
target: "browser",
|
|
563
|
-
naming: "[name].[ext]",
|
|
564
|
-
external: [...SHELL_BASE_EXTERNAL, ...otherExternals],
|
|
565
|
-
define: {
|
|
566
|
-
ONLY_SERVER: "false",
|
|
567
|
-
ONLY_BROWSER: "true",
|
|
568
|
-
ONLY_CLIENT: "true",
|
|
569
|
-
},
|
|
570
|
-
});
|
|
571
|
-
if (!r.success) {
|
|
572
|
-
for (const l of r.logs) console.error(l);
|
|
573
|
-
throw new Error(`Shell build failed for ${pkg}`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const outputHash = sha256OfFiles([outputFile]);
|
|
577
|
-
updateCache(cache, unitId, inputHash, { outputHash });
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Build the framework shell — react layer + each @arcote.tech/* package as a
|
|
582
|
-
* separate cacheable unit. Tasks run in parallel via pAll.
|
|
583
|
-
*/
|
|
584
|
-
export async function buildShell(
|
|
585
|
-
ws: WorkspaceInfo,
|
|
586
|
-
cache: BuildCache,
|
|
587
|
-
noCache: boolean,
|
|
588
|
-
): Promise<void> {
|
|
589
|
-
mkdirSync(ws.shellDir, { recursive: true });
|
|
590
|
-
const tmpDir = join(ws.shellDir, "_tmp");
|
|
591
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
592
|
-
|
|
593
|
-
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
594
|
-
const allArcPkgs = arcEntries.map(([, pkg]) => pkg);
|
|
595
|
-
|
|
596
|
-
const tasks: Array<() => Promise<void>> = [
|
|
597
|
-
() => buildShellReact(ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
|
|
598
|
-
...arcEntries.map(([short, pkg]) => () =>
|
|
599
|
-
buildShellArcEntry(short, pkg, allArcPkgs, ws.shellDir, tmpDir, ws.rootDir, cache, noCache),
|
|
600
|
-
),
|
|
601
|
-
];
|
|
602
|
-
|
|
603
|
-
await pAll(tasks);
|
|
604
|
-
|
|
605
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
606
|
-
}
|
|
607
|
-
|
|
608
396
|
// ---------------------------------------------------------------------------
|
|
609
397
|
// Server context loading
|
|
610
398
|
// ---------------------------------------------------------------------------
|
|
@@ -618,9 +406,9 @@ export async function loadServerContext(
|
|
|
618
406
|
(globalThis as any).ONLY_BROWSER = false;
|
|
619
407
|
(globalThis as any).ONLY_CLIENT = false;
|
|
620
408
|
|
|
621
|
-
// Resolve platform from the project's node_modules.
|
|
622
|
-
//
|
|
623
|
-
//
|
|
409
|
+
// Resolve platform from the project's node_modules. Platform exports a
|
|
410
|
+
// single entry (./src/index.ts) — React imports at top level are benign
|
|
411
|
+
// in a non-render context (createContext, function defs only).
|
|
624
412
|
const platformDir = join(process.cwd(), "node_modules", "@arcote.tech", "platform");
|
|
625
413
|
const platformPkg = JSON.parse(readFileSync(join(platformDir, "package.json"), "utf-8"));
|
|
626
414
|
const platformEntry = join(platformDir, platformPkg.main ?? "src/index.ts");
|
package/src/platform/startup.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { startPlatformServer, type PlatformServer } from "./server";
|
|
|
4
4
|
import type { BuildManifest } from "./shared";
|
|
5
5
|
import {
|
|
6
6
|
buildAll,
|
|
7
|
-
collectArcPeerDeps,
|
|
8
7
|
err,
|
|
9
8
|
loadServerContext,
|
|
10
9
|
log,
|
|
@@ -54,7 +53,7 @@ export async function startPlatform(
|
|
|
54
53
|
if (devMode) {
|
|
55
54
|
manifest = await buildAll(ws);
|
|
56
55
|
} else {
|
|
57
|
-
const manifestPath = join(ws.
|
|
56
|
+
const manifestPath = join(ws.arcDir, "manifest.json");
|
|
58
57
|
if (!existsSync(manifestPath)) {
|
|
59
58
|
err("No build found. Run `arc platform build` first.");
|
|
60
59
|
process.exit(1);
|
|
@@ -75,7 +74,6 @@ export async function startPlatform(
|
|
|
75
74
|
|
|
76
75
|
// 3. Start the platform server. Cache headers + SSE behaviour are
|
|
77
76
|
// controlled inside the server by `devMode`; we just pass the flag.
|
|
78
|
-
const arcEntries = collectArcPeerDeps(ws.packages);
|
|
79
77
|
const platform = await startPlatformServer({
|
|
80
78
|
ws,
|
|
81
79
|
port,
|
|
@@ -84,7 +82,6 @@ export async function startPlatform(
|
|
|
84
82
|
moduleAccess,
|
|
85
83
|
dbPath,
|
|
86
84
|
devMode,
|
|
87
|
-
arcEntries,
|
|
88
85
|
});
|
|
89
86
|
|
|
90
87
|
ok(`Server on http://localhost:${port}`);
|
|
@@ -121,7 +118,9 @@ function attachDevWatcher(ws: WorkspaceInfo, platform: PlatformServer): void {
|
|
|
121
118
|
const next = await buildAll(ws);
|
|
122
119
|
platform.setManifest(next);
|
|
123
120
|
platform.notifyReload(next);
|
|
124
|
-
ok(
|
|
121
|
+
ok(
|
|
122
|
+
`Rebuilt — initial + ${Object.keys(next.groups).length} group(s)`,
|
|
123
|
+
);
|
|
125
124
|
} catch (e) {
|
|
126
125
|
console.error(`Rebuild failed: ${e}`);
|
|
127
126
|
} finally {
|