isthmus-cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +49 -28
- package/README.md +55 -31
- package/Skills/isthmus/SKILL.md +93 -4
- package/dist/cli/command-support.d.ts +1 -1
- package/dist/cli/command-support.js +2 -2
- package/dist/cli/command-support.js.map +1 -1
- package/dist/cli/impact-command.d.ts +5 -0
- package/dist/cli/impact-command.js +111 -0
- package/dist/cli/impact-command.js.map +1 -0
- package/dist/cli/main.js +15 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/preflight-command.d.ts +5 -0
- package/dist/cli/preflight-command.js +83 -0
- package/dist/cli/preflight-command.js.map +1 -0
- package/dist/cli/runtime-command.d.ts +5 -0
- package/dist/cli/runtime-command.js +42 -0
- package/dist/cli/runtime-command.js.map +1 -0
- package/dist/cli/runtime-json-reader.d.ts +12 -0
- package/dist/cli/runtime-json-reader.js +37 -0
- package/dist/cli/runtime-json-reader.js.map +1 -0
- package/dist/exchange/impact-selection.d.ts +13 -0
- package/dist/exchange/impact-selection.js +35 -0
- package/dist/exchange/impact-selection.js.map +1 -0
- package/dist/exchange/kartograph-impact.d.ts +4 -0
- package/dist/exchange/kartograph-impact.js +169 -0
- package/dist/exchange/kartograph-impact.js.map +1 -0
- package/dist/exchange/messages.d.ts +51 -0
- package/dist/exchange/messages.js +126 -0
- package/dist/exchange/messages.js.map +1 -0
- package/dist/exchange/parse.d.ts +8 -0
- package/dist/exchange/parse.js +12 -8
- package/dist/exchange/parse.js.map +1 -1
- package/dist/exchange/preflight-context.d.ts +71 -0
- package/dist/exchange/preflight-context.js +317 -0
- package/dist/exchange/preflight-context.js.map +1 -0
- package/dist/exchange/producer-impact.d.ts +17 -0
- package/dist/exchange/producer-impact.js +226 -0
- package/dist/exchange/producer-impact.js.map +1 -0
- package/dist/exchange/runtime.d.ts +69 -0
- package/dist/exchange/runtime.js +160 -0
- package/dist/exchange/runtime.js.map +1 -0
- package/dist/join/message-address.d.ts +7 -0
- package/dist/join/message-address.js +37 -0
- package/dist/join/message-address.js.map +1 -0
- package/dist/join/messages.d.ts +23 -0
- package/dist/join/messages.js +80 -0
- package/dist/join/messages.js.map +1 -0
- package/dist/report/diff.js +4 -3
- package/dist/report/diff.js.map +1 -1
- package/dist/report/impact.d.ts +62 -0
- package/dist/report/impact.js +168 -0
- package/dist/report/impact.js.map +1 -0
- package/dist/report/preflight-runtime.d.ts +42 -0
- package/dist/report/preflight-runtime.js +193 -0
- package/dist/report/preflight-runtime.js.map +1 -0
- package/dist/report/preflight-view.d.ts +146 -0
- package/dist/report/preflight-view.js +189 -0
- package/dist/report/preflight-view.js.map +1 -0
- package/dist/report/preflight.d.ts +104 -0
- package/dist/report/preflight.js +296 -0
- package/dist/report/preflight.js.map +1 -0
- package/dist/report/runtime-impact.d.ts +38 -0
- package/dist/report/runtime-impact.js +94 -0
- package/dist/report/runtime-impact.js.map +1 -0
- package/dist/report/runtime.d.ts +54 -0
- package/dist/report/runtime.js +137 -0
- package/dist/report/runtime.js.map +1 -0
- package/dist/report/sorted-json.d.ts +1 -1
- package/dist/report/sorted-json.js +2 -2
- package/dist/report/sorted-json.js.map +1 -1
- package/docs/BRIDGE-MESSAGES.md +102 -0
- package/docs/GRAPH-EXCHANGE.md +255 -0
- package/docs/IMPACT.md +96 -0
- package/docs/PREFLIGHT.md +295 -0
- package/docs/RUNTIME.md +172 -0
- package/docs/TOOLCHAIN.md +101 -0
- package/package.json +11 -2
- package/scripts/build-preflight-toolchain.mjs +186 -0
- package/scripts/capture-preflight.mjs +392 -0
- package/scripts/run-child.mjs +16 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 고정 소스에서 사전 점검 도구 구축
|
|
2
|
+
|
|
3
|
+
개발 소스의 절차이며 현재 공개된 isthmus 0.5.0에는 새 preflight 기능이 없다.
|
|
4
|
+
이 절차는 로컬 Git 저장소의 **지정한 commit만** 새 디렉터리에 풀어 선택한 도구를 구축한다.
|
|
5
|
+
원본의 미커밋 수정, dist, .build, .dart_tool을 복사하지 않는다. SDK와 전역 의존성 캐시는
|
|
6
|
+
공유할 수 있으므로 빈 머신 전체 설치 시간과 구분한다. 소스 다운로드·발행은 수행하지 않는다.
|
|
7
|
+
|
|
8
|
+
## 준비
|
|
9
|
+
|
|
10
|
+
- Node.js 22.18.0 이상, Dart 3.11 이상.
|
|
11
|
+
- 로컬 Git checkout: isthmus·dartograph와 native producer 하나 이상.
|
|
12
|
+
- Swift 분석에는 cartograph와 Swift 6 toolchain, Android 분석에는 kartograph와 JDK 17+가 필요하다.
|
|
13
|
+
Android만 구축할 때는 Swift toolchain을 호출하지 않는다. JDK 경로는 `JAVA_HOME`으로 지정한다.
|
|
14
|
+
- 선택한 Cartograph commit은 `impact`와 `bridges --messages`를 함께 제공해야 한다.
|
|
15
|
+
Dartograph는 `bridges --messages`, isthmus는 `preflight --summary/--explain`이 필요하다.
|
|
16
|
+
- 선택한 Kartograph commit은 `impact --graph-file`과 `bridges --messages --graph-file`을 제공해야 한다.
|
|
17
|
+
- 앱의 Swift 인덱스와 실제 Flutter 검증에는 해당 앱의 Flutter/Xcode 환경이 추가로 필요하다.
|
|
18
|
+
도구 자체를 구축하는 과정은 Flutter SDK를 필수로 요구하지 않는다.
|
|
19
|
+
|
|
20
|
+
아직 이 기능을 모두 포함한 공개 호환 버전은 확정·발행하지 않았다. 저장소 checkout의
|
|
21
|
+
`docs/COMPETITIVENESS.md`에서 검증 중인 개발 commit과 남은 작업을 확인할 수 있다.
|
|
22
|
+
기능 이름만 보고 기존 발행 버전을 사용하지 않는다. 소스 commit이 로컬 Git에 있어야 하며
|
|
23
|
+
빌드 과정의 npm/pub/SwiftPM/Gradle 의존성 해석은 네트워크를 사용할 수 있다.
|
|
24
|
+
|
|
25
|
+
## 실행
|
|
26
|
+
|
|
27
|
+
`build.json`에 실제 로컬 경로와 `git rev-parse <ref>`로 확인한 전체 40자리 commit을 적는다.
|
|
28
|
+
아래 `<...>`는 대체해야 할 값이다. 상대 경로는 JSON 파일의 디렉터리를 기준으로 해석한다.
|
|
29
|
+
destination은 존재하지 않아야 하고 그 부모 디렉터리는 미리 준비한다.
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"format": "isthmus-toolchain-build",
|
|
34
|
+
"version": 1,
|
|
35
|
+
"destination": "../built-isthmus-tools",
|
|
36
|
+
"repositories": {
|
|
37
|
+
"isthmus": {"path": "../isthmus", "revision": "<40-character-commit>"},
|
|
38
|
+
"cartograph": {"path": "../cartograph", "revision": "<40-character-commit>"},
|
|
39
|
+
"dartograph": {"path": "../dartograph", "revision": "<40-character-commit>"}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
node scripts/build-preflight-toolchain.mjs build.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Android만 필요하면 위 repositories의 `cartograph`를 `kartograph`로 바꾸고 실제 Kotlin
|
|
49
|
+
producer checkout과 commit을 지정한다. 두 native producer를 함께 넣으면 네 도구를 구축한다.
|
|
50
|
+
Kartograph는 Gradle `:cli:installDist`로 만든 배포 스크립트와 runtime JAR을 사용한다.
|
|
51
|
+
|
|
52
|
+
스크립트는 commit 확인 → source archive → 선택한 native producer 빌드 → Dart AOT 빌드 → npm tarball 생성·
|
|
53
|
+
격리 설치 순서로 실행한다. 기존 destination을 덮어쓰지 않고, 실패 시 자신이 새로 만든
|
|
54
|
+
destination만 정리한다. 각 단계의 로그는 별도의 임시 근거 디렉터리에 남기며 경로를 알린다.
|
|
55
|
+
성공한 디렉터리는 옮기지 않고 그대로 사용한다.
|
|
56
|
+
|
|
57
|
+
`<destination>/toolchain.json`에는 source commit, SDK 버전, 실행 파일/패키지 hash, 단계별
|
|
58
|
+
시간과 아래 명령 배열이 있다.
|
|
59
|
+
|
|
60
|
+
- `commands.isthmus`: Node와 설치된 CLI의 경로.
|
|
61
|
+
- `commands.cartograph`: impact와 Basic을 함께 제공하는 실행 파일 경로.
|
|
62
|
+
- `commands.kartograph`: Android 분석용 배포 스크립트 경로(선택한 경우).
|
|
63
|
+
- `commands.dartograph`: AOT 실행 파일 경로.
|
|
64
|
+
|
|
65
|
+
`artifacts.kartographInputs`는 실행 스크립트와 `lib/` 디렉터리다. 둘 다 capture의 toolInputs에
|
|
66
|
+
사용하며 launcher hash 외에 `hashes.kartographLibraries`로 runtime JAR 묶음도 기록한다.
|
|
67
|
+
|
|
68
|
+
이 성공은 구축과 CLI 기능 확인이다. 앱 분석의 정확성·모든 런타임 의존성 검증을 뜻하지 않는다.
|
|
69
|
+
|
|
70
|
+
## 앱 수집 및 CI 연결
|
|
71
|
+
|
|
72
|
+
[PREFLIGHT.md](PREFLIGHT.md)의 capture 설정에서 위 두 producer 명령을 사용하고 Basic이
|
|
73
|
+
필요하면 `"messages": true`를 지정한다. 서로 다른 Cartograph 실행 파일을 나눠 쓸 필요가 없다.
|
|
74
|
+
실행 파일과 앱 소스·생성 설정·SDK 입력을 `toolInputs`/`inputs`에 선언한다.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
node <isthmus-source>/scripts/capture-preflight.mjs capture.json
|
|
78
|
+
node <installed-isthmus-main.js> preflight context.json --summary --strict --compact
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
첫 명령은 기존 설정의 prepare 명령으로 앱 인덱스를 갱신하고 내용 해시가 같은 수집 결과만
|
|
82
|
+
재사용한다. 두 번째 명령의 exit 1은 검토할 결과이며 JSON을 버리지 않는다. 공개 Pigeon의
|
|
83
|
+
prefix와 실행하지 않은 시나리오 등은 의도적으로 공백으로 남을 수 있다.
|
|
84
|
+
|
|
85
|
+
CI에서는 도구 구축과 앱 수집을 구분한다. 도구는 검증한 commit의 빌드 산출물을 사용하고,
|
|
86
|
+
앱 입력이 바뀔 때 capture를 갱신한다. 저장소의 소비자 성능 검사는 5초 예산을 검사하지만
|
|
87
|
+
producer 및 앱 빌드 시간은 포함하지 않는다. 원격 CI 실행과 최초 외부 사용자의 구축은
|
|
88
|
+
로컬 source archive 검증과 별도로 확인해야 한다.
|
|
89
|
+
|
|
90
|
+
## 실제 Dart 바인딩 상태 대조
|
|
91
|
+
|
|
92
|
+
저장소 검증 스크립트는 Flutter의 실제 BasicMessageChannel API를 mock messenger와 함께
|
|
93
|
+
실행해 mutable 필드, 고정 필드, 순차 대입, then/else에서 관찰한 주소를 producer와 대조한다.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
node scripts/verify-dart-binding-runtime.mjs <flutter> <dartograph-aot>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
같은 mutable 필드 호출이 호출 이력에 따라 다른 주소를 쓰면 정적 단일 주소로 확정하지
|
|
100
|
+
않아야 한다. 반대로 고정 필드와 명확한 직선 흐름은 실제 주소를 유지해야 한다.
|
|
101
|
+
이 검증은 Dart/Flutter 바인딩 상태의 실행이며 native IPC나 앱 전체 동작 검증은 아니다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "isthmus-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Join bridge facts across cross-platform application boundaries.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"flutter",
|
|
@@ -28,6 +28,15 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
30
|
"Skills",
|
|
31
|
+
"scripts/capture-preflight.mjs",
|
|
32
|
+
"scripts/run-child.mjs",
|
|
33
|
+
"scripts/build-preflight-toolchain.mjs",
|
|
34
|
+
"docs/TOOLCHAIN.md",
|
|
35
|
+
"docs/PREFLIGHT.md",
|
|
36
|
+
"docs/RUNTIME.md",
|
|
37
|
+
"docs/IMPACT.md",
|
|
38
|
+
"docs/BRIDGE-MESSAGES.md",
|
|
39
|
+
"docs/GRAPH-EXCHANGE.md",
|
|
31
40
|
"LICENSE",
|
|
32
41
|
"README.md",
|
|
33
42
|
"README.ko.md"
|
|
@@ -43,7 +52,7 @@
|
|
|
43
52
|
"verify:build": "node scripts/verify-build-contract.mjs",
|
|
44
53
|
"verify:cli": "npm run verify:build && node scripts/verify-cli-contract.mjs",
|
|
45
54
|
"verify:package": "npm run verify:build && node scripts/verify-package-contract.mjs",
|
|
46
|
-
"verify": "npm run typecheck && npm test && npm run test:phase0:join && npm run verify:build && node scripts/verify-cli-contract.mjs && node scripts/verify-package-contract.mjs",
|
|
55
|
+
"verify": "npm run typecheck && npm test && npm run test:phase0:join && npm run verify:build && node --test scripts/capture-preflight.test.mjs scripts/build-preflight-toolchain.test.mjs scripts/stop-detached-process.test.mjs && node scripts/verify-cli-contract.mjs && node scripts/verify-package-contract.mjs",
|
|
47
56
|
"prepublishOnly": "npm run verify"
|
|
48
57
|
},
|
|
49
58
|
"devDependencies": {
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { runChild } from './run-child.mjs';
|
|
7
|
+
|
|
8
|
+
const names = ['isthmus', 'cartograph', 'dartograph', 'kartograph'];
|
|
9
|
+
|
|
10
|
+
/** 변경 중인 작업 트리 대신 명시한 immutable commit의 소스만 구축한다. */
|
|
11
|
+
export function parseToolchainManifest(input) {
|
|
12
|
+
if (!record(input) || input.format !== 'isthmus-toolchain-build' || input.version !== 1 ||
|
|
13
|
+
!text(input.destination) || !record(input.repositories) ||
|
|
14
|
+
Object.keys(input.repositories).some((name) => !names.includes(name))) throw new Error('Invalid toolchain build manifest.');
|
|
15
|
+
if (input.repositories.cartograph === undefined && input.repositories.kartograph === undefined) {
|
|
16
|
+
throw new Error('Configure at least one native producer repository.');
|
|
17
|
+
}
|
|
18
|
+
const repositories = {};
|
|
19
|
+
for (const name of names) {
|
|
20
|
+
const entry = input.repositories[name];
|
|
21
|
+
if ((name === 'cartograph' || name === 'kartograph') && entry === undefined) continue;
|
|
22
|
+
if (!record(entry) || !text(entry.path) || typeof entry.revision !== 'string' || !/^[a-f0-9]{40}$/u.test(entry.revision)) {
|
|
23
|
+
throw new Error('Each toolchain repository requires a local path and full commit id.');
|
|
24
|
+
}
|
|
25
|
+
repositories[name] = { path: entry.path, revision: entry.revision };
|
|
26
|
+
}
|
|
27
|
+
return { format: input.format, version: 1, destination: input.destination, repositories };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 새 디렉터리에 source archive·실행 파일·설치된 CLI와 재사용할 명령 목록을 만든다. */
|
|
31
|
+
export async function buildToolchain(input, base = process.cwd(), onStep = () => {}) {
|
|
32
|
+
const config = parseToolchainManifest(input);
|
|
33
|
+
const selectedNames = names.filter((name) => config.repositories[name] !== undefined);
|
|
34
|
+
const destination = resolve(base, config.destination);
|
|
35
|
+
if (await exists(destination)) throw new Error('Toolchain destination already exists; choose a new directory.');
|
|
36
|
+
const repositories = {};
|
|
37
|
+
// source commit 오류를 발견했을 때 SDK 준비나 destination 쓰기를 시작하지 않는다.
|
|
38
|
+
for (const name of selectedNames) {
|
|
39
|
+
const path = await realpath(resolve(base, config.repositories[name].path));
|
|
40
|
+
const revision = config.repositories[name].revision;
|
|
41
|
+
const result = runChild('git', ['-c', 'core.fsmonitor=false', '-C', path, 'rev-parse', '--verify', `${revision}^{commit}`]);
|
|
42
|
+
if (result.status !== 0 || result.stdout.trim() !== revision) throw new Error(`Unable to resolve ${name} commit.`);
|
|
43
|
+
repositories[name] = { path, revision };
|
|
44
|
+
}
|
|
45
|
+
const evidence = await mkdtemp(join(tmpdir(), 'isthmus-toolchain-build-evidence-'));
|
|
46
|
+
const steps = [];
|
|
47
|
+
const started = performance.now();
|
|
48
|
+
let created = false;
|
|
49
|
+
const environment = { ...process.env, CI: 'true', FLUTTER_SUPPRESS_ANALYTICS: 'true',
|
|
50
|
+
GIT_TERMINAL_PROMPT: '0', LLVM_PROFILE_FILE: join(evidence, 'profile-%p.profraw') };
|
|
51
|
+
async function command(label, executable, args, cwd = destination, timeout = 600_000) {
|
|
52
|
+
onStep(label);
|
|
53
|
+
const began = performance.now();
|
|
54
|
+
const result = runChild(executable, args, { cwd, timeout, env: environment, maxBuffer: 32 * 1024 * 1024 });
|
|
55
|
+
const log = join(evidence, `${steps.length}-${label}.log`);
|
|
56
|
+
await writeFile(log, `${result.stdout ?? ''}\n${result.stderr ?? ''}`, { mode: 0o600 });
|
|
57
|
+
steps.push({ label, milliseconds: Math.round(performance.now() - began), exitCode: result.status });
|
|
58
|
+
if (result.status !== 0 || result.error) throw new Error(`Toolchain step failed: ${label}; see ${log}`);
|
|
59
|
+
return result.stdout.trim();
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await mkdir(destination, { mode: 0o700 }); created = true;
|
|
63
|
+
for (const name of selectedNames) {
|
|
64
|
+
const source = join(destination, 'sources', name);
|
|
65
|
+
const archive = join(evidence, `${name}.tar`);
|
|
66
|
+
await mkdir(source, { recursive: true });
|
|
67
|
+
await command(`${name}-archive`, 'git', ['-c', 'core.fsmonitor=false', '-C', repositories[name].path,
|
|
68
|
+
'archive', '--format=tar', '--output', archive, repositories[name].revision]);
|
|
69
|
+
await command(`${name}-extract`, 'tar', ['-xf', archive, '-C', source]);
|
|
70
|
+
repositories[name].checkout = source;
|
|
71
|
+
}
|
|
72
|
+
const commands = {};
|
|
73
|
+
const nativeFiles = [];
|
|
74
|
+
const artifacts = {};
|
|
75
|
+
let swiftVersion;
|
|
76
|
+
let javaVersion;
|
|
77
|
+
const dartVersion = await command('dart-version', 'dart', ['--version']);
|
|
78
|
+
if (repositories.cartograph !== undefined) {
|
|
79
|
+
swiftVersion = await command('swift-version', 'swift', ['--version']);
|
|
80
|
+
const native = repositories.cartograph.checkout;
|
|
81
|
+
await command('cartograph-build', 'swift', ['build', '--package-path', native, '--configuration', 'debug', '--skip-update']);
|
|
82
|
+
const reported = await command('cartograph-bin-path', 'swift', ['build', '--package-path', native, '--configuration', 'debug', '--show-bin-path']);
|
|
83
|
+
const candidates = [join(native, '.build/out/Products/Debug/cartograph'), join(reported, 'cartograph')];
|
|
84
|
+
const binaries = [...new Set(await Promise.all(candidates.map(async (path) => {
|
|
85
|
+
try { return await realpath(path); } catch { return undefined; }
|
|
86
|
+
})))].filter(Boolean);
|
|
87
|
+
if (binaries.length !== 1) throw new Error('Swift build did not identify one cartograph executable.');
|
|
88
|
+
const cartograph = binaries[0];
|
|
89
|
+
await command('cartograph-impact-help', cartograph, ['impact', '--help']);
|
|
90
|
+
const messageHelp = await command('cartograph-messages-help', cartograph, ['bridges', '--help']);
|
|
91
|
+
if (!messageHelp.includes('--messages')) throw new Error('The cartograph commit does not support message facts.');
|
|
92
|
+
commands.cartograph = [cartograph];
|
|
93
|
+
nativeFiles.push(['cartograph', cartograph]);
|
|
94
|
+
}
|
|
95
|
+
if (repositories.kartograph !== undefined) {
|
|
96
|
+
javaVersion = await command('java-version', process.env.JAVA_HOME ? join(process.env.JAVA_HOME, 'bin/java') : 'java', ['--version']);
|
|
97
|
+
const kotlinRoot = repositories.kartograph.checkout;
|
|
98
|
+
await command('kartograph-build', join(kotlinRoot, 'gradlew'), ['--no-daemon', ':cli:installDist'], kotlinRoot);
|
|
99
|
+
const kartograph = join(kotlinRoot, 'cli/build/install/kartograph/bin/kartograph');
|
|
100
|
+
const libraryRoot = join(kotlinRoot, 'cli/build/install/kartograph/lib');
|
|
101
|
+
await command('kartograph-impact-help', kartograph, ['impact', '--help']);
|
|
102
|
+
const bridgeHelp = await command('kartograph-messages-help', kartograph, ['bridges', '--help']);
|
|
103
|
+
if (!bridgeHelp.includes('--messages') || !bridgeHelp.includes('--graph-file')) {
|
|
104
|
+
throw new Error('The kartograph commit does not support indexed message facts.');
|
|
105
|
+
}
|
|
106
|
+
commands.kartograph = [kartograph];
|
|
107
|
+
nativeFiles.push(['kartograph', kartograph]);
|
|
108
|
+
artifacts.kartographInputs = [kartograph, libraryRoot];
|
|
109
|
+
}
|
|
110
|
+
const dartRoot = repositories.dartograph.checkout;
|
|
111
|
+
await command('dartograph-dependencies', 'dart', ['pub', 'get'], dartRoot);
|
|
112
|
+
await mkdir(join(destination, 'bin'));
|
|
113
|
+
const dartograph = join(destination, 'bin/dartograph');
|
|
114
|
+
await command('dartograph-build', 'dart', ['compile', 'exe', 'bin/dartograph.dart', '-o', dartograph], dartRoot);
|
|
115
|
+
const dartHelp = await command('dartograph-messages-help', dartograph, ['--help']);
|
|
116
|
+
if (!dartHelp.includes('--messages')) throw new Error('The dartograph commit does not support message facts.');
|
|
117
|
+
const nodeRoot = repositories.isthmus.checkout;
|
|
118
|
+
await command('isthmus-dependencies', 'npm', ['ci'], nodeRoot);
|
|
119
|
+
await command('isthmus-build', 'npm', ['run', 'build'], nodeRoot);
|
|
120
|
+
const packed = JSON.parse(await command('isthmus-pack', 'npm', ['pack', '--ignore-scripts', '--json', '--pack-destination', destination], nodeRoot));
|
|
121
|
+
if (packed.length !== 1 || packed[0].name !== 'isthmus-cli') throw new Error('Unexpected isthmus package artifact.');
|
|
122
|
+
const install = join(destination, 'installed');
|
|
123
|
+
await command('isthmus-install', 'npm', ['install', '--prefix', install, '--offline', '--ignore-scripts', '--no-audit', '--no-fund', join(destination, packed[0].filename)]);
|
|
124
|
+
const isthmus = join(install, 'node_modules/isthmus-cli/dist/cli/main.js');
|
|
125
|
+
const help = await command('isthmus-preflight-help', process.execPath, [isthmus, 'preflight', '--help']);
|
|
126
|
+
if (!help.includes('--summary') || !help.includes('--explain')) throw new Error('The isthmus commit does not support bounded preflight views.');
|
|
127
|
+
if (repositories.kartograph !== undefined) {
|
|
128
|
+
const context = join(evidence, 'android-context.json');
|
|
129
|
+
await writeFile(context, JSON.stringify({ format: 'isthmus-preflight-context', version: 1,
|
|
130
|
+
project: destination, revision: 'toolchain-capability', selection: {}, bindings: [], analyses: [], limitations: [],
|
|
131
|
+
bridges: ['dart', 'kotlin'].map((platform) => ({ format: 'bridge-facts', version: 1, platform, target: null,
|
|
132
|
+
project: destination, generatedAt: '1970-01-01T00:00:00Z', tool: { name: platform, version: 'capability' },
|
|
133
|
+
facts: [], limitations: [] })) }), { mode: 0o600 });
|
|
134
|
+
const report = JSON.parse(await command('isthmus-kotlin-preflight', process.execPath, [isthmus, 'preflight', context, '--summary']));
|
|
135
|
+
if (report.format !== 'isthmus-preflight-summary') throw new Error('The isthmus commit does not support Kotlin preflight inputs.');
|
|
136
|
+
}
|
|
137
|
+
commands.isthmus = [process.execPath, isthmus];
|
|
138
|
+
commands.dartograph = [dartograph];
|
|
139
|
+
const hashes = {};
|
|
140
|
+
for (const [name, path] of [...nativeFiles, ['dartograph', dartograph], ['isthmusPackage', join(destination, packed[0].filename)]]) {
|
|
141
|
+
hashes[name] = createHash('sha256').update(await readFile(path)).digest('hex');
|
|
142
|
+
}
|
|
143
|
+
if (artifacts.kartographInputs) {
|
|
144
|
+
const libraries = artifacts.kartographInputs[1];
|
|
145
|
+
const hash = createHash('sha256');
|
|
146
|
+
const jars = (await readdir(libraries)).filter((name) => name.endsWith('.jar')).sort();
|
|
147
|
+
if (jars.length === 0) throw new Error('Kartograph distribution has no runtime libraries.');
|
|
148
|
+
for (const name of jars) hash.update(name).update('\0').update(createHash('sha256').update(await readFile(join(libraries, name))).digest());
|
|
149
|
+
hashes.kartographLibraries = hash.digest('hex');
|
|
150
|
+
}
|
|
151
|
+
const result = { format: 'isthmus-built-toolchain', version: 1, destination, repositories, commands, hashes,
|
|
152
|
+
...(Object.keys(artifacts).length === 0 ? {} : { artifacts }),
|
|
153
|
+
environment: { node: process.version, platform: process.platform, arch: process.arch,
|
|
154
|
+
...(swiftVersion === undefined ? {} : { swift: swiftVersion }), ...(javaVersion === undefined ? {} : { java: javaVersion }), dart: dartVersion },
|
|
155
|
+
scope: 'committed-source-build-and-cli-capabilities', milliseconds: Math.round(performance.now() - started), evidence, steps };
|
|
156
|
+
await writeFile(join(destination, 'toolchain.json'), JSON.stringify(result, null, 2), { mode: 0o600 });
|
|
157
|
+
return result;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
await writeFile(join(evidence, 'failure.json'), JSON.stringify({ steps, failed: true }, null, 2), { mode: 0o600 });
|
|
160
|
+
if (created) await rm(destination, { recursive: true, force: true });
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function record(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
|
|
166
|
+
function text(value) { return typeof value === 'string' && value.length > 0 && !/[\u0000-\u001f]/u.test(value); }
|
|
167
|
+
async function exists(path) {
|
|
168
|
+
try { await lstat(path); return true; } catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
172
|
+
if (process.argv.length !== 3) {
|
|
173
|
+
process.stderr.write('Usage: build-preflight-toolchain.mjs <build-manifest.json>\n'); process.exitCode = 64;
|
|
174
|
+
} else {
|
|
175
|
+
try {
|
|
176
|
+
const path = resolve(process.argv[2]);
|
|
177
|
+
const result = await buildToolchain(JSON.parse(await readFile(path, 'utf8')), dirname(path),
|
|
178
|
+
(step) => process.stderr.write(`Toolchain: ${step}\n`));
|
|
179
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const message = error instanceof SyntaxError ? 'Build manifest or package metadata is not valid JSON.'
|
|
182
|
+
: typeof error.code === 'string' ? 'Unable to access a required toolchain file or process.' : error.message;
|
|
183
|
+
process.stderr.write(`Toolchain build failed: ${message}\n`); process.exitCode = 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|