@appforge-ci/admin-cli 0.3.8 → 0.3.9
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/commands/warm-cache.d.ts +8 -0
- package/dist/commands/warm-cache.d.ts.map +1 -0
- package/dist/commands/warm-cache.js +19 -0
- package/dist/commands/warm-cache.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/warm-cache.d.ts +57 -0
- package/dist/lib/warm-cache.d.ts.map +1 -0
- package/dist/lib/warm-cache.js +143 -0
- package/dist/lib/warm-cache.js.map +1 -0
- package/dist/lib/warm-cache.test.d.ts +2 -0
- package/dist/lib/warm-cache.test.d.ts.map +1 -0
- package/dist/lib/warm-cache.test.js +114 -0
- package/dist/lib/warm-cache.test.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `appforge-admin warm-cache` — fills a regulated org's offline package cache from its Flutter app's lock file, then
|
|
4
|
+
* proves it resolves offline. Run ON the Mac, in your own terminal (the build user's commands prompt for sudo as
|
|
5
|
+
* needed). Regulated builds have no route to pub.dev, so this is how their dependencies get into the cache.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerWarmCacheCommand(program: Command): void;
|
|
8
|
+
//# sourceMappingURL=warm-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warm-cache.d.ts","sourceRoot":"","sources":["../../src/commands/warm-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,QAaxD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { realWarmCacheDeps, warmOrgCache } from "../lib/warm-cache.js";
|
|
2
|
+
/**
|
|
3
|
+
* `appforge-admin warm-cache` — fills a regulated org's offline package cache from its Flutter app's lock file, then
|
|
4
|
+
* proves it resolves offline. Run ON the Mac, in your own terminal (the build user's commands prompt for sudo as
|
|
5
|
+
* needed). Regulated builds have no route to pub.dev, so this is how their dependencies get into the cache.
|
|
6
|
+
*/
|
|
7
|
+
export function registerWarmCacheCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command("warm-cache")
|
|
10
|
+
.description("Fill a regulated org's offline pub cache from a Flutter app's pubspec.lock, then verify it resolves offline (run ON the Mac)")
|
|
11
|
+
.requiredOption("--org <orgId>", "the customer org whose regulated builds will use this cache")
|
|
12
|
+
.requiredOption("--project <dir>", "the Flutter app's directory (its pubspec.yaml and pubspec.lock)")
|
|
13
|
+
.option("--flutter-sdk <clone>", "which of the org's cached Flutter SDK clones to use (a directory name, or a path); default: the only one")
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
const result = await warmOrgCache({ orgId: opts.org, projectDir: opts.project, flutterSdk: opts.flutterSdk }, realWarmCacheDeps());
|
|
16
|
+
console.log(`\nDone: ${result.packages} hosted package(s) cached for this org, verified offline with ${result.clone}.`);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=warm-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warm-cache.js","sourceRoot":"","sources":["../../src/commands/warm-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAgB;IACvD,OAAO;SACJ,OAAO,CAAC,YAAY,CAAC;SACrB,WAAW,CACV,8HAA8H,CAC/H;SACA,cAAc,CAAC,eAAe,EAAE,6DAA6D,CAAC;SAC9F,cAAc,CAAC,iBAAiB,EAAE,iEAAiE,CAAC;SACpG,MAAM,CAAC,uBAAuB,EAAE,0GAA0G,CAAC;SAC3I,MAAM,CAAC,KAAK,EAAE,IAA2D,EAAE,EAAE;QAC5E,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,iBAAiB,EAAE,CAAC,CAAC;QACnI,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,QAAQ,iEAAiE,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;IAC1H,CAAC,CAAC,CAAC;AACP,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { apiErrorMessage } from "@appforge-ci/core";
|
|
|
4
4
|
import { registerLoginCommand } from "./commands/login.js";
|
|
5
5
|
import { registerAgentsCommands } from "./commands/agents.js";
|
|
6
6
|
import { registerVpnHostsCommands } from "./commands/vpn-hosts.js";
|
|
7
|
+
import { registerWarmCacheCommand } from "./commands/warm-cache.js";
|
|
7
8
|
/**
|
|
8
9
|
* appforge-admin — fleet management for the AppForge platform operator.
|
|
9
10
|
*
|
|
@@ -24,6 +25,7 @@ program
|
|
|
24
25
|
registerLoginCommand(program);
|
|
25
26
|
registerAgentsCommands(program);
|
|
26
27
|
registerVpnHostsCommands(program);
|
|
28
|
+
registerWarmCacheCommand(program);
|
|
27
29
|
program.parseAsync(process.argv).catch((err) => {
|
|
28
30
|
// Preserves the original "print whatever was thrown" fallback for a
|
|
29
31
|
// non-Error throw (rare, but not this fix's concern) — only the
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEpE;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAC9B,OAAO;KACJ,IAAI,CAAC,gBAAgB,CAAC;KACtB,WAAW,CAAC,uFAAuF,CAAC;KACpG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,wBAAwB,CAAC,OAAO,CAAC,CAAC;AAClC,wBAAwB,CAAC,OAAO,CAAC,CAAC;AAElC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC7C,oEAAoE;IACpE,gEAAgE;IAChE,2DAA2D;IAC3D,sEAAsE;IACtE,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hosted packages a pubspec.lock pins, as the `name-version` directory names pub uses in its cache. Git, path
|
|
3
|
+
* and sdk packages are not in the hosted tree, so they are skipped. pub writes the lock in one fixed two-level
|
|
4
|
+
* layout: `packages:`, then ` name:`, then indented `source:` and `version:` lines.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hostedPackagesFromLock(lockText: string): string[];
|
|
7
|
+
/**
|
|
8
|
+
* Which cloned Flutter SDK to warm against. The lock's solver depends on the SDK version, so the warm-up must use
|
|
9
|
+
* the same clone the builds use. `explicit` is a clone's directory name under the org's `flutter-sdk/`, or an
|
|
10
|
+
* absolute path. Without it, the org must have exactly one complete clone.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pickSdkClone(orgSdkDir: string, completeClones: readonly string[], explicit?: string): string;
|
|
13
|
+
export interface WarmCacheOptions {
|
|
14
|
+
orgId: string;
|
|
15
|
+
/** The Flutter app's directory: its pubspec.yaml and pubspec.lock. */
|
|
16
|
+
projectDir: string;
|
|
17
|
+
/** A clone's directory name, or an absolute path. Default: the org's only complete clone. */
|
|
18
|
+
flutterSdk?: string;
|
|
19
|
+
user?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Every filesystem and command action the warm-up takes, behind one seam so the whole sequence is unit-tested. */
|
|
22
|
+
export interface WarmCacheDeps {
|
|
23
|
+
readText(path: string): string;
|
|
24
|
+
exists(path: string): boolean;
|
|
25
|
+
listDirs(path: string): string[];
|
|
26
|
+
orgCacheDir(orgId: string): string;
|
|
27
|
+
checkBuildUser(user: string): Promise<{
|
|
28
|
+
installed: boolean;
|
|
29
|
+
reason: string;
|
|
30
|
+
}>;
|
|
31
|
+
buildUserDirs(user: string): Promise<{
|
|
32
|
+
temp: string;
|
|
33
|
+
cache: string;
|
|
34
|
+
}>;
|
|
35
|
+
/** Copies the named files into a new directory the build user can read, and returns its path. */
|
|
36
|
+
stage(projectDir: string, names: readonly string[]): string;
|
|
37
|
+
unstage(dir: string): void;
|
|
38
|
+
/** Runs `argv` as the build user, streaming its output to the terminal. Rejects on a non-zero exit. */
|
|
39
|
+
runAsBuildUser(user: string, argv: string[]): Promise<void>;
|
|
40
|
+
/** Runs `argv` as the build user and returns its stdout. Rejects on a non-zero exit. */
|
|
41
|
+
captureAsBuildUser(user: string, argv: string[]): Promise<string>;
|
|
42
|
+
randomId(): string;
|
|
43
|
+
log(line: string): void;
|
|
44
|
+
}
|
|
45
|
+
export interface WarmCacheResult {
|
|
46
|
+
packages: number;
|
|
47
|
+
clone: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fills the org's offline package cache from a project's lock, then proves it. The lock is resolved online once, as
|
|
51
|
+
* the build user, into the org's pub cache. A fresh workspace must then resolve the same lock with `--offline`, from
|
|
52
|
+
* that cache alone. A regulated build runs with no route to pub.dev, so this is how its dependencies get there.
|
|
53
|
+
*/
|
|
54
|
+
export declare function warmOrgCache(opts: WarmCacheOptions, deps: WarmCacheDeps): Promise<WarmCacheResult>;
|
|
55
|
+
/** The real seam: the agent's own cache and build-user helpers, the terminal, and a staging directory. */
|
|
56
|
+
export declare function realWarmCacheDeps(): WarmCacheDeps;
|
|
57
|
+
//# sourceMappingURL=warm-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warm-cache.d.ts","sourceRoot":"","sources":["../../src/lib/warm-cache.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAyBjE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5G;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mHAAmH;AACnH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACjC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9E,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,iGAAiG;IACjG,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC;IAC5D,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uGAAuG;IACvG,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,wFAAwF;IACxF,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClE,QAAQ,IAAI,MAAM,CAAC;IACnB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAOD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CA8CxG;AAED,0GAA0G;AAC1G,wBAAgB,iBAAiB,IAAI,aAAa,CA6BjD"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { BUILD_USER_NAME, buildUserDarwinDirs, detectBuildUser, realBuildUserProbeDeps } from "@appforge-ci/agent/dist/lib/sandbox/build-user.js";
|
|
5
|
+
import { cacheRootDir } from "@appforge-ci/agent/dist/lib/sandbox/cache.js";
|
|
6
|
+
/** The file a finished org cache leaves in its Flutter SDK clone (see the agent's toolchain.ts). Only such a clone is used. */
|
|
7
|
+
const CLONE_COMPLETE_MARKER = ".appforge-clone-complete";
|
|
8
|
+
/**
|
|
9
|
+
* The hosted packages a pubspec.lock pins, as the `name-version` directory names pub uses in its cache. Git, path
|
|
10
|
+
* and sdk packages are not in the hosted tree, so they are skipped. pub writes the lock in one fixed two-level
|
|
11
|
+
* layout: `packages:`, then ` name:`, then indented `source:` and `version:` lines.
|
|
12
|
+
*/
|
|
13
|
+
export function hostedPackagesFromLock(lockText) {
|
|
14
|
+
const out = [];
|
|
15
|
+
let name = null;
|
|
16
|
+
let source = null;
|
|
17
|
+
let version = null;
|
|
18
|
+
const flush = () => {
|
|
19
|
+
if (name && source === "hosted" && version)
|
|
20
|
+
out.push(`${name}-${version}`);
|
|
21
|
+
};
|
|
22
|
+
for (const raw of lockText.split("\n")) {
|
|
23
|
+
const line = raw.replace(/\r$/, "");
|
|
24
|
+
const top = /^ {2}([A-Za-z0-9_]+):$/.exec(line);
|
|
25
|
+
if (top) {
|
|
26
|
+
flush();
|
|
27
|
+
name = top[1];
|
|
28
|
+
source = null;
|
|
29
|
+
version = null;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const src = /^ {4}source: (\S+)/.exec(line);
|
|
33
|
+
if (src)
|
|
34
|
+
source = src[1];
|
|
35
|
+
const ver = /^ {4}version: "([^"]+)"/.exec(line);
|
|
36
|
+
if (ver)
|
|
37
|
+
version = ver[1];
|
|
38
|
+
}
|
|
39
|
+
flush();
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Which cloned Flutter SDK to warm against. The lock's solver depends on the SDK version, so the warm-up must use
|
|
44
|
+
* the same clone the builds use. `explicit` is a clone's directory name under the org's `flutter-sdk/`, or an
|
|
45
|
+
* absolute path. Without it, the org must have exactly one complete clone.
|
|
46
|
+
*/
|
|
47
|
+
export function pickSdkClone(orgSdkDir, completeClones, explicit) {
|
|
48
|
+
if (explicit)
|
|
49
|
+
return explicit.startsWith("/") ? explicit : join(orgSdkDir, explicit);
|
|
50
|
+
if (completeClones.length === 0)
|
|
51
|
+
throw new Error("this org has no cached Flutter SDK clone yet — run one build for it first, or pass --flutter-sdk");
|
|
52
|
+
if (completeClones.length > 1)
|
|
53
|
+
throw new Error(`this org has ${completeClones.length} cached Flutter SDK clones (${completeClones.join(", ")}); pass --flutter-sdk to choose one`);
|
|
54
|
+
return join(orgSdkDir, completeClones[0]);
|
|
55
|
+
}
|
|
56
|
+
/** The environment the agent gives a pub step (the same variables), as `env -i` assignments. */
|
|
57
|
+
function pubEnv(home, tmp, clone, pubCache) {
|
|
58
|
+
return [`HOME=${home}`, `TMPDIR=${tmp}`, `PATH=${join(clone, "bin")}:/usr/bin:/bin:/usr/sbin:/sbin`, `PUB_CACHE=${pubCache}`, "LANG=en_US.UTF-8"];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Fills the org's offline package cache from a project's lock, then proves it. The lock is resolved online once, as
|
|
62
|
+
* the build user, into the org's pub cache. A fresh workspace must then resolve the same lock with `--offline`, from
|
|
63
|
+
* that cache alone. A regulated build runs with no route to pub.dev, so this is how its dependencies get there.
|
|
64
|
+
*/
|
|
65
|
+
export async function warmOrgCache(opts, deps) {
|
|
66
|
+
const user = opts.user ?? BUILD_USER_NAME;
|
|
67
|
+
const status = await deps.checkBuildUser(user);
|
|
68
|
+
if (!status.installed) {
|
|
69
|
+
throw new Error(`the build user '${user}' is not ready on this Mac (${status.reason}); run 'appforge-admin agents install-build-user' first`);
|
|
70
|
+
}
|
|
71
|
+
const lockPath = join(opts.projectDir, "pubspec.lock");
|
|
72
|
+
const yamlPath = join(opts.projectDir, "pubspec.yaml");
|
|
73
|
+
if (!deps.exists(lockPath) || !deps.exists(yamlPath))
|
|
74
|
+
throw new Error(`the project directory needs both pubspec.yaml and pubspec.lock: ${opts.projectDir}`);
|
|
75
|
+
const packages = hostedPackagesFromLock(deps.readText(lockPath));
|
|
76
|
+
const orgCache = deps.orgCacheDir(opts.orgId);
|
|
77
|
+
const orgSdk = join(orgCache, "flutter-sdk");
|
|
78
|
+
const complete = deps.listDirs(orgSdk).filter((n) => deps.exists(join(orgSdk, n, CLONE_COMPLETE_MARKER)));
|
|
79
|
+
const clone = pickSdkClone(orgSdk, complete, opts.flutterSdk);
|
|
80
|
+
const flutter = join(clone, "bin", "flutter");
|
|
81
|
+
if (!deps.exists(flutter))
|
|
82
|
+
throw new Error(`no Flutter binary in ${clone}`);
|
|
83
|
+
const pubCache = join(orgCache, "pub");
|
|
84
|
+
deps.log(`==> Warming ${opts.orgId}'s pub cache for ${packages.length} hosted package(s), resolved as '${user}' with ${clone}`);
|
|
85
|
+
const stage = deps.stage(opts.projectDir, ["pubspec.yaml", "pubspec.lock"]);
|
|
86
|
+
const dirs = await deps.buildUserDirs(user);
|
|
87
|
+
const root = join(dirs.temp, `warm-${deps.randomId()}`);
|
|
88
|
+
const home = join(root, "home");
|
|
89
|
+
const tmp = join(root, "tmp");
|
|
90
|
+
const online = join(root, "online");
|
|
91
|
+
const offline = join(root, "offline");
|
|
92
|
+
try {
|
|
93
|
+
await deps.captureAsBuildUser(user, ["/bin/mkdir", "-p", home, tmp, online, offline]);
|
|
94
|
+
// Copied as the build user, because the build user is the one that must read the staging directory.
|
|
95
|
+
await deps.captureAsBuildUser(user, ["/bin/sh", "-c", 'cp "$1/pubspec.yaml" "$1/pubspec.lock" "$2/" && cp "$1/pubspec.yaml" "$1/pubspec.lock" "$3/"', "sh", stage, online, offline]);
|
|
96
|
+
// `env -i` clears the inherited environment, so every variable a pub step needs is listed explicitly. The
|
|
97
|
+
// directory is changed into in the shell, and then the real command replaces it.
|
|
98
|
+
const pubCommand = (dir, extra) => ["/usr/bin/env", "-i", ...pubEnv(home, tmp, clone, pubCache), "/bin/sh", "-c", 'cd "$1" && shift && exec "$@"', "sh", dir, flutter, "pub", "get", ...extra];
|
|
99
|
+
deps.log("==> Online: resolving the lock against the registry");
|
|
100
|
+
await deps.runAsBuildUser(user, pubCommand(online, []));
|
|
101
|
+
deps.log("==> Offline check: a fresh workspace must resolve from the cache alone");
|
|
102
|
+
const check = await deps.captureAsBuildUser(user, pubCommand(offline, ["--offline"]));
|
|
103
|
+
if (!check.includes("Got dependencies"))
|
|
104
|
+
throw new Error("the offline check did not resolve the lock from the cache; the warm-up is incomplete");
|
|
105
|
+
deps.log(`==> Warmed and verified offline: ${packages.length} hosted package(s) in ${pubCache}`);
|
|
106
|
+
return { packages: packages.length, clone };
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
await deps.captureAsBuildUser(user, ["/bin/rm", "-rf", "--", root]).catch(() => undefined);
|
|
110
|
+
deps.unstage(stage);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** The real seam: the agent's own cache and build-user helpers, the terminal, and a staging directory. */
|
|
114
|
+
export function realWarmCacheDeps() {
|
|
115
|
+
return {
|
|
116
|
+
readText: (p) => readFileSync(p, "utf8"),
|
|
117
|
+
exists: (p) => existsSync(p),
|
|
118
|
+
listDirs: (p) => (existsSync(p) ? readdirSync(p).filter((n) => statSync(join(p, n)).isDirectory()) : []),
|
|
119
|
+
orgCacheDir: (orgId) => join(cacheRootDir(), orgId),
|
|
120
|
+
checkBuildUser: (user) => detectBuildUser(user),
|
|
121
|
+
buildUserDirs: (user) => buildUserDarwinDirs(user, realBuildUserProbeDeps),
|
|
122
|
+
stage: (projectDir, names) => {
|
|
123
|
+
// /private/tmp is readable by the build user; mkdtemp's own 0700 would hide the files from it.
|
|
124
|
+
const dir = mkdtempSync("/private/tmp/appforge-warm-");
|
|
125
|
+
chmodSync(dir, 0o755);
|
|
126
|
+
for (const name of names) {
|
|
127
|
+
copyFileSync(join(projectDir, name), join(dir, name));
|
|
128
|
+
chmodSync(join(dir, name), 0o644);
|
|
129
|
+
}
|
|
130
|
+
return dir;
|
|
131
|
+
},
|
|
132
|
+
unstage: (dir) => rmSync(dir, { recursive: true, force: true }),
|
|
133
|
+
runAsBuildUser: (user, argv) => new Promise((resolve, reject) => {
|
|
134
|
+
const child = spawn("/usr/bin/sudo", ["-n", "-u", user, "--", "/usr/bin/sandbox-exec", "-p", "(version 1)(allow default)", ...argv], { stdio: "inherit" });
|
|
135
|
+
child.on("error", reject);
|
|
136
|
+
child.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`the step exited with code ${code}`))));
|
|
137
|
+
}),
|
|
138
|
+
captureAsBuildUser: (user, argv) => realBuildUserProbeDeps.runAsBuildUser(user, argv),
|
|
139
|
+
randomId: () => Math.random().toString(36).slice(2, 10),
|
|
140
|
+
log: (l) => console.log(l),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=warm-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warm-cache.js","sourceRoot":"","sources":["../../src/lib/warm-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warm-cache.test.d.ts","sourceRoot":"","sources":["../../src/lib/warm-cache.test.ts"],"names":[],"mappings":"AACA,OAAO,iCAAiC,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Must stay the FIRST import — see ../test-support/home-sandbox.ts.
|
|
2
|
+
import "../test-support/home-sandbox.js";
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { hostedPackagesFromLock, pickSdkClone, warmOrgCache } from "./warm-cache.js";
|
|
6
|
+
const LOCK = `# Generated by pub
|
|
7
|
+
packages:
|
|
8
|
+
flutter_lints:
|
|
9
|
+
dependency: "direct dev"
|
|
10
|
+
description:
|
|
11
|
+
name: flutter_lints
|
|
12
|
+
sha256: "abc"
|
|
13
|
+
url: "https://pub.dev"
|
|
14
|
+
source: hosted
|
|
15
|
+
version: "6.0.0"
|
|
16
|
+
my_local:
|
|
17
|
+
dependency: "direct main"
|
|
18
|
+
description:
|
|
19
|
+
path: "../my_local"
|
|
20
|
+
relative: true
|
|
21
|
+
source: path
|
|
22
|
+
version: "0.0.1"
|
|
23
|
+
flutter:
|
|
24
|
+
dependency: "direct main"
|
|
25
|
+
description: flutter
|
|
26
|
+
source: sdk
|
|
27
|
+
version: "0.0.0"
|
|
28
|
+
sdks:
|
|
29
|
+
dart: ">=3.13.1 <4.0.0"
|
|
30
|
+
`;
|
|
31
|
+
test("hostedPackagesFromLock lists only the hosted packages, as name-version, and skips path and sdk packages", () => {
|
|
32
|
+
assert.deepEqual(hostedPackagesFromLock(LOCK), ["flutter_lints-6.0.0"]);
|
|
33
|
+
assert.deepEqual(hostedPackagesFromLock(LOCK.replace(/\n/g, "\r\n")), ["flutter_lints-6.0.0"], "a CRLF lock parses the same");
|
|
34
|
+
assert.deepEqual(hostedPackagesFromLock(""), []);
|
|
35
|
+
});
|
|
36
|
+
test("pickSdkClone uses the one complete clone, takes an explicit name or path, and refuses to guess among several", () => {
|
|
37
|
+
assert.equal(pickSdkClone("/o/flutter-sdk", ["abc"]), "/o/flutter-sdk/abc");
|
|
38
|
+
assert.equal(pickSdkClone("/o/flutter-sdk", ["abc", "def"], "def"), "/o/flutter-sdk/def");
|
|
39
|
+
assert.equal(pickSdkClone("/o/flutter-sdk", [], "/elsewhere/clone"), "/elsewhere/clone");
|
|
40
|
+
assert.throws(() => pickSdkClone("/o/flutter-sdk", []), /no cached Flutter SDK clone/);
|
|
41
|
+
assert.throws(() => pickSdkClone("/o/flutter-sdk", ["abc", "def"]), /2 cached Flutter SDK clones/);
|
|
42
|
+
});
|
|
43
|
+
function fakeDeps(over = {}) {
|
|
44
|
+
const calls = [];
|
|
45
|
+
const logs = [];
|
|
46
|
+
const files = new Map([
|
|
47
|
+
["/proj/pubspec.yaml", "name: app\n"],
|
|
48
|
+
["/proj/pubspec.lock", LOCK],
|
|
49
|
+
["/org/flutter-sdk/abc/bin/flutter", ""],
|
|
50
|
+
["/org/flutter-sdk/abc/.appforge-clone-complete", "ok\n"],
|
|
51
|
+
]);
|
|
52
|
+
const deps = {
|
|
53
|
+
readText: (p) => {
|
|
54
|
+
const t = files.get(p);
|
|
55
|
+
if (t === undefined)
|
|
56
|
+
throw new Error(`no ${p}`);
|
|
57
|
+
return t;
|
|
58
|
+
},
|
|
59
|
+
exists: (p) => files.has(p),
|
|
60
|
+
listDirs: (p) => (p === "/org/flutter-sdk" ? ["abc"] : []),
|
|
61
|
+
orgCacheDir: () => "/org",
|
|
62
|
+
checkBuildUser: async () => ({ installed: true, reason: "ok" }),
|
|
63
|
+
buildUserDirs: async () => ({ temp: "/bu/T", cache: "/bu/C" }),
|
|
64
|
+
stage: () => "/stage",
|
|
65
|
+
unstage: () => {
|
|
66
|
+
calls.push({ kind: "unstage" });
|
|
67
|
+
},
|
|
68
|
+
runAsBuildUser: async (user, argv) => {
|
|
69
|
+
calls.push({ kind: "online", user, argv });
|
|
70
|
+
},
|
|
71
|
+
captureAsBuildUser: async (user, argv) => {
|
|
72
|
+
calls.push({ kind: "capture", user, argv });
|
|
73
|
+
return argv.includes("--offline") ? "Resolving dependencies...\nGot dependencies!\n" : "";
|
|
74
|
+
},
|
|
75
|
+
randomId: () => "r1",
|
|
76
|
+
log: (l) => logs.push(l),
|
|
77
|
+
...over,
|
|
78
|
+
};
|
|
79
|
+
return { deps, calls, logs };
|
|
80
|
+
}
|
|
81
|
+
test("warmOrgCache resolves the lock online as the build user into the org's pub cache, verifies it offline in a fresh workspace, and cleans up", async () => {
|
|
82
|
+
const { deps, calls } = fakeDeps();
|
|
83
|
+
const result = await warmOrgCache({ orgId: "org1", projectDir: "/proj" }, deps);
|
|
84
|
+
assert.deepEqual(result, { packages: 1, clone: "/org/flutter-sdk/abc" });
|
|
85
|
+
const online = calls.find((c) => c.kind === "online");
|
|
86
|
+
assert.equal(online.user, "_appforgebuild");
|
|
87
|
+
assert.ok(online.argv.includes("PUB_CACHE=/org/pub"), "the org's own pub cache, not the operator's");
|
|
88
|
+
assert.ok(!online.argv.includes("--offline"), "the online pass is the one allowed to reach the registry");
|
|
89
|
+
const offline = calls.find((c) => c.kind === "capture" && c.argv.includes("--offline"));
|
|
90
|
+
assert.match(offline.argv.join(" "), /\/warm-r1\/offline/, "the offline check runs in a fresh workspace, not the one the online pass warmed");
|
|
91
|
+
assert.ok(calls.some((c) => c.kind === "capture" && c.argv.includes("/bin/rm")), "the workspace is removed");
|
|
92
|
+
assert.equal(calls.at(-1)?.kind, "unstage", "and the staging copy is removed last");
|
|
93
|
+
});
|
|
94
|
+
test("warmOrgCache refuses, and touches nothing, when the build user is not ready on this Mac", async () => {
|
|
95
|
+
const { deps, calls } = fakeDeps({ checkBuildUser: async () => ({ installed: false, reason: "no account" }) });
|
|
96
|
+
await assert.rejects(warmOrgCache({ orgId: "org1", projectDir: "/proj" }, deps), /not ready on this Mac \(no account\); run 'appforge-admin agents install-build-user' first/);
|
|
97
|
+
assert.equal(calls.length, 0);
|
|
98
|
+
});
|
|
99
|
+
test("warmOrgCache fails loudly when the offline check cannot resolve from the cache, and still removes the workspace and the staging copy", async () => {
|
|
100
|
+
const { deps, calls } = fakeDeps({
|
|
101
|
+
captureAsBuildUser: async (user, argv) => {
|
|
102
|
+
calls.push({ kind: "capture", user, argv });
|
|
103
|
+
return argv.includes("--offline") ? "Because the app depends on flutter_lints any which doesn't exist" : "";
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
await assert.rejects(warmOrgCache({ orgId: "org1", projectDir: "/proj" }, deps), /did not resolve the lock from the cache/);
|
|
107
|
+
assert.ok(calls.some((c) => c.argv?.includes("/bin/rm")), "the workspace is removed even on failure");
|
|
108
|
+
assert.equal(calls.at(-1)?.kind, "unstage");
|
|
109
|
+
});
|
|
110
|
+
test("warmOrgCache needs both pubspec.yaml and pubspec.lock in the project", async () => {
|
|
111
|
+
const { deps } = fakeDeps({ exists: () => false });
|
|
112
|
+
await assert.rejects(warmOrgCache({ orgId: "org1", projectDir: "/proj" }, deps), /needs both pubspec\.yaml and pubspec\.lock/);
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=warm-cache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warm-cache.test.js","sourceRoot":"","sources":["../../src/lib/warm-cache.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appforge-ci/admin-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"chalk": "^5.3.0",
|
|
17
17
|
"commander": "^12.1.0",
|
|
18
|
-
"@appforge-ci/agent": "0.3.
|
|
19
|
-
"@appforge-ci/core": "0.3.
|
|
18
|
+
"@appforge-ci/agent": "0.3.9",
|
|
19
|
+
"@appforge-ci/core": "0.3.9"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^20.16.5",
|