IM-Juggling構成を操作する
@intra-mart/juggling-core は IM-Juggling(intra-mart のモジュール構成・war 生成ツール)のドメインロジックを TypeScript ライブラリ化したものです。カタログ取得 → プロジェクト作成 → モジュール選択 → 設定配備 → war / 静的ファイル生成 → パッチ適用 → バージョンアップ までの全ユースケースを、GUI に依存せず API から実行できます。
目次
- 0. 動作環境とセットアップ
- 1. プロジェクトの作成
- 2. プロジェクトを開く
- 3. モジュールの選択
- 4. ユーザモジュールの追加
- 5. 設定ファイルの配備
- 6. war ファイルの生成
- 7. 静的ファイルの生成
- 8. パッチの検索と適用
- 9. バージョンアップ
- 10. 設定の変更・リモートリポジトリの変更
- 付録 A. 集約ファサード早見表
- 付録 B. 「戻り値で表現される業務的失敗」一覧
0. 動作環境とセットアップ
| 項目 | 内容 |
|---|---|
| パッケージ名 | @intra-mart/juggling-core |
| モジュール形式 | ESM のみ(CommonJS は非提供) |
| Node.js | 22.19.0 以上が必須。本パッケージは undici@8 に依存し、Node 20 では import 時に TypeError: webidl.util.markAsUncloneable is not a function で失敗します。package.json の engines も >=22.19.0 |
| Bun | 1.3 以降でも動作します |
| ネットワーク | 既定ではリモートリポジトリ(http://repository.intra-mart.jp/…)へ HTTP アクセスします |
インストール
mkdir my-juggling && cd my-juggling
npm init -y
npm pkg set type=module # ESM 前提
npm i @intra-mart/juggling-core
npm i -D tsx typescript @types/node # 実行(tsx)・型検査(tsc)用
TypeScript の tsconfig.json は NodeNext 解決にします。
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"exactOptionalPropertyTypes": true,
"skipLibCheck": true
}
}
公開サブパス
ルート(@intra-mart/juggling-core)は集約ファサードを提供します。加えて用途別サブパスがあります。
| import パス | 主な内容 |
|---|---|
@intra-mart/juggling-core | createJuggling()(集約ファサード)、ModuleKey、parseVersion、主要型の再エクスポート |
@intra-mart/juggling-core/model | ModuleKey / parseVersion / CategoryData / CategoryModule などモデル型 |
@intra-mart/juggling-core/rules | 選択候補フィルタ(filterApplicationCandidates ほか)・検証(checkBaseSelection ほか) |
@intra-mart/juggling-core/project | ModuleStructure 操作・computeCheckState・設定配備の低レベル関数 |
@intra-mart/juggling-core/build | ビルドテンプレート・createImuiScriptPort など |
@intra-mart/juggling-core/runtime/node | Node ランタイムポート(createNodeFileSystemPort など) |
0.1 集約ファサード createJuggling() の全体像
createJuggling(options?) は非同期で「リポジトリの初期化(setup)」まで済ませたファサードを返します。
ほとんどの操作はこの戻り値 juggling を起点にたどれます。
import { createJuggling } from "@intra-mart/juggling-core";
const juggling = await createJuggling({
managedRepositoryPath: "./.managed", // ローカルの管理リポジトリ(モジュールキャッシュ)
locale: "ja", // 省略時はホスト環境のロケール
// repositories: [...] // 省略時は intra-mart 既定リモートを使用(後述 §10)
});
console.log(Object.keys(juggling).sort());
実行結果(実測)
[ 'build', 'catalog', 'context', 'moduleService',
'projects', 'repositories', 'repositoryService', 'rules' ]
主なオプション(CreateJugglingOptions。すべて任意):
| オプション | 既定 | 説明 |
|---|---|---|
managedRepositoryPath | ~/.juggling/repository | 取得済みモジュールを貯めるローカルキャッシュ。明示指定を推奨 |
locale | ホスト環境 | Java ロケール表記("ja" / "en" / "zh_CN")。カタログ名の解決に使用 |
repositories | 既定リモート2件 | リポジトリ設定(§10)。明示指定が最優先 |
runtime | runtime/node を自動ロード | I/O ポート束。バンドラ利用時は明示注入 |
enableVisa | false | 取得物の署名検証 |
onSuppressedError | — | 「握りつぶす I/O 失敗」を観測するコールバック |
signal | — | 初回 setup のキャンセル |
戻り値 juggling の8プロパティ:
| プロパティ | 役割 |
|---|---|
juggling.catalog | カタログ取得(ベース/アプリ/推奨/パッチ照会) |
juggling.projects | プロジェクトの create / createFromRecommended / open |
juggling.build | templates / plan / exportWar / exportStatic |
juggling.repositories | リポジトリ設定の取得/保存/初期化(§10) |
juggling.repositoryService | モジュール/メタデータ取得(リトライ適用済み) |
juggling.moduleService | 単体 .imm の変換など |
juggling.rules | ルールレジストリ(エディション種別一覧など) |
juggling.context | ロケール・ランタイム等の実行コンテキスト |
作業終了時はリポジトリのハンドルを解放します。
await juggling.repositories.close();
0.2 エラー設計(例外か戻り値か)
juggling-core は失敗の種類で表現方法を分けています。マニュアル全体で共通する重要な前提です。
- I/O・整合性エラー → 型付き例外(
BuildError/JugglingError/ArgumentErrorなど)を throw。 - 「業務ルール上の不成立」 → 例外ではなく戻り値で表現。
- 検証結果:
AggregateStatus(severity: "OK" | "WARNING" | "NG")。 - ユーザモジュール追加:
AddUserModulesResult(kind: "completed" | "rejected" | "rolled-back" | "aborted")。 - 設定一括配備:
DeployBatchResult(abortedフィールド)。 - パッチ/アップグレード適用:
ApplyPatchesResult/ApplyUpgradeResult(applied: true | false)。
- 検証結果:
呼び出し側は try/catch と「戻り値の判別」の両方でハンドリングします(付録 B)。
1. プロジェクトの作成
目的: カタログからベースとアプリケーションを選び、新しい Juggling プロジェクト(<dir>/juggling.im を持つ
ディレクトリ)を作成する。
1.1 カタログの取得(ベース/アプリ/推奨)
手順: juggling.catalog からベース一覧・アプリ一覧・推奨構成一覧を取得します。カテゴリ(CategoryData)は
availableModules() で「利用可能なモジュールのみ」を返します。各モジュール(CategoryModule)は .id / .version /
.status / .requiredProducts を持ちます(モジュール自体に名称プロパティはありません)。名称はカテゴリ側の
CategoryData.name(LocalizedText | null)にあり、.get() で解決値・.raw で原文を得ます。
const bases = await juggling.catalog.getBaseCategories(); // CategoryData[]
const apps = await juggling.catalog.getApplicationCategories(); // CategoryData[]
const recommended = await juggling.catalog.getRecommendedData();
console.log("base categories:", bases.length);
console.log("app categories:", apps.length);
console.log("recommended:", recommended.length);
for (const cat of bases.slice(0, 3)) {
console.log(cat.id, "|", cat.name?.get() ?? "", "| modules:", cat.availableModules().length);
}
const firstBase = bases.flatMap((c) => c.availableModules())[0]!;
console.log("first base module:", String(firstBase.id), firstBase.version.toString(), firstBase.status);
実行結果(実測)
base categories: 7
app categories: 48
recommended: 6
jp.co.intra_mart.module.pack.im_basic.category | intra-mart Accel Platform Basic Edition | modules: 7
jp.co.intra_mart.module.pack.im_advance.category | intra-mart Accel Platform Advance Edition | modules: 6
jp.co.intra_mart.module.pack.im_professional.category | intra-mart Accel Platform Professional Edition | modules: 7
first base module: jp.co.intra_mart.module.pack.im_basic 8.0.33 available
補足: 同一ベース製品は id でグルーピングし、
version.compareToで降順に並べると UI 表示に向いた一覧になります。
1.2 ベースから対象アプリを絞り込む
アプリ一覧(48カテゴリ / 616モジュール)には、選択したベースに適合しないアプリも含まれます。ベースを決めたら、
@intra-mart/juggling-core/rules の filterApplicationCandidates で「そのベースに載せられるアプリ」に絞り込みます。
この絞り込みは、API 一覧を眺めるだけでは気付きにくい中間手順の代表例です。
絞り込みロジック: 各アプリモジュールの requiredProducts(OR グループの集合)のいずれかの product が
ベースの id+version に一致すれば候補になります。requiredProducts が空のアプリは無条件で候補です。
import { filterApplicationCandidates } from "@intra-mart/juggling-core/rules";
const bases = await juggling.catalog.getBaseCategories();
const base = bases
.flatMap((c) => c.availableModules())
.find((m) => String(m.id) === "jp.co.intra_mart.module.pack.im_basic")!;
const appCats = await juggling.catalog.getApplicationCategories();
console.log("before filter:", appCats.flatMap((c) => c.availableModules()).length, "modules");
const candidateCats = filterApplicationCandidates({
base: { id: String(base.id), version: base.version.toString() }, // BaseRef = { id, version(文字列) }
categories: appCats,
filters: [], // 追加フィルタは通常なし
});
const candidateModules = candidateCats.flatMap((c) => c.availableModules());
console.log("after filter:", candidateCats.length, "categories /", candidateModules.length, "modules");
実行結果(実測。ベース = im_basic 8.0.33)
before filter: 616 modules
after filter: 21 categories / 48 modules
つまり 616 → 48 に絞り込まれます。候補には例えば im_theme_customize_pack(標準テーマカスタマイズ)、
iac_suite_pack(Accel Collaboration)、im_spreadsheet_pack などが含まれます。
エディション種別でベースを絞る(任意): filterBaseCategories を使います。
import { filterBaseCategories, EditionKindSelection } from "@intra-mart/juggling-core/rules";
const kinds = juggling.rules.editionKinds.list(); // 例: edition-for-package / edition-for-csl
const narrowedBases = filterBaseCategories(bases, new EditionKindSelection(kinds.slice(0, 1)));
console.log("bases after edition filter:", narrowedBases.length); // → 2
1.3 選択内容の検証
作成前に、選択の妥当性を @intra-mart/juggling-core/rules の検証関数で確認できます。返るのは
RuleFinding | null(問題なければ null)。メッセージ解決には createRuleMessageResolver(locale) を使います。
import {
checkBaseSelection,
checkApplicationSelection,
checkProductSelection,
createRuleMessageResolver,
} from "@intra-mart/juggling-core/rules";
const app = candidateModules.find((m) => String(m.id) === "jp.co.intra_mart.module.pack.im_theme_customize_pack")!;
const resolver = createRuleMessageResolver(juggling.context.locale);
const findings = [
checkBaseSelection(base, resolver),
checkApplicationSelection(
{ base: { id: String(base.id), version: base.version.toString() }, candidates: candidateCats, selection: [app] },
resolver,
),
checkProductSelection([base, app]),
].filter((f) => f && f.message);
console.log("findings:", findings.length); // 問題なければ 0
実行結果(実測): findings: 0
「モジュール不足(MV-01)」のような構成の欠落は、ここではなく作成後の
project.validate()(§1.6)が担当します。
1.4 プロジェクトを作成する
juggling.projects.create(req) にベース・アプリ・作成先ディレクトリを渡します。戻り値は JugglingProject
(以降のすべての操作の起点)。onProgress で進捗を受け取れます(タスク名 "プロジェクトの作成中: "、total 2500)。
const project = await juggling.projects.create({
dir: "./.work/proj1", // 既存ディレクトリは NP-01 で拒否される
name: "sample-basic", // .project の名前
base, // CategoryModule(1件)
applications: [app], // CategoryModule[]
// additionalResourcePlatformIds: [...], // 省略時は default=true かつ対象のものを配備
onProgress: (e) => console.log(e.task, e.total),
});
console.log("dir:", project.dir);
console.log("base:", String(project.latest.getBase()?.key().id));
console.log("apps:", project.latest.getApplications().length);
実行結果(実測)
プロジェクトの作成中: 2500
dir: /…/.work/proj1
base: jp.co.intra_mart.module.pack.im_basic
apps: 1
推奨構成から作成する場合は createFromRecommended を使います。入力が recommended: RecommendedData になる点が
異なります。getRecommendedData() は取得元情報付きの WithRepository<RecommendedData>[] を返すため、.data で
アンラップします。
const recs = await juggling.catalog.getRecommendedData(); // WithRepository<RecommendedData>[]
const project2 = await juggling.projects.createFromRecommended({
dir: "./.work/proj-rec",
name: "from-recommended",
recommended: recs[0]!.data,
});
1.5 追加リソースの配備
アプリケーションサーバ向けの追加リソース(起動スクリプト等)を後付けで配備できます。現在の構成
(base+application の製品 id)を対象とするプラットフォーム定義は listAdditionalResourcePlatforms() で得られます。
const platforms = project.listAdditionalResourcePlatforms(); // AdditionalResourcePlatform[]
console.log("platforms:", platforms.map((p) => `${p.id}(default=${p.default})`).slice(0, 4));
// 既定対象(default=true)だけを配備する例。`to` が既に存在するファイルは上書きせずスキップ。
const defaults = platforms.filter((p) => p.default).map((p) => p.id);
await project.deployAdditionalResources(defaults);
実行結果(実測): platforms: [ 'resin-4.0(default=true)', 'payara-5(default=false)', 'weblogic-12c(default=false)', 'seasar2-sastruts(default=false)' ](全18件)
AdditionalResourcePlatform は { id, name, default, requireModulePackIds, description, resources }。
1.6 プロジェクトの検査(validate)
project.validate() は「conf の存在チェック + ユーザモジュール再読込」をファイルから取り直したうえで構造を検証し、
AggregateStatus を返します(例外ではなく値)。
const status = await project.validate(); // AggregateStatus
console.log("severity:", status.severity, "| findings:", status.flatten().length);
for (const f of status.flatten()) {
console.log(f.severity, f.kind, f.ruleId, "-", f.message);
}
実行結果(実測。作成直後): severity: OK | findings: 0
AggregateStatus = { severity: "OK"|"WARNING"|"NG"; children; isOk(); isWarning(); isNg(); flatten() }。
個々の指摘 ValidationStatus = { severity, kind, ruleId, messageId, message, target?, notes? }。
kind は "missing-module" | "missing-configuration" | "invalid-user-module" | "duplicate-id" | "duplicate-short-id" | "rule"。
1.7 プロジェクトの保存
保存は履歴遷移を伴います。planSave() で「保存時にどの遷移になるか(説明文が必須か)」を事前確認でき、
save(opts?) で実際に永続化します。<dir>/juggling.im に書き込まれます。
console.log("isModified:", project.isModified());
const plan = await project.planSave(); // SavePlan { transition, diffs }
const t = plan.transition;
const requiresDescription =
t.kind === "overwrite" || t.kind === "append" ? t.requiresDescription : false;
console.log("transition:", t.kind, "| requiresDescription:", requiresDescription);
const result = await project.save({ description: "初期構成" }); // SaveResult
console.log("saved:", result.transition.kind, "| status:", result.status.severity);
console.log("history versions:", project.history.getAllVersions().length);
実行結果(実測)
isModified: false
transition: overwrite-unchanged | requiresDescription: false
saved: overwrite-unchanged | status: OK
history versions: 1
createは内部で初回保存まで済ませるため、直後はisModified()===false・遷移はoverwrite-unchangedに なります。HistoryTransitionは判別 union で、requiresDescriptionを持つのはoverwrite/appendのときだけです。
注意点:
requiresDescriptionな遷移でdescriptionを省略するとArgumentError(空文字列""は有効)。- 過去履歴をすべて削除して最新1件に切り詰めるには
await project.compact()。
2. プロジェクトを開く
目的: 保存済みプロジェクトを読み込み、JugglingProject を復元する。
手順: juggling.projects.open(dir, opts?)。<dir>/juggling.im を読み、リポジトリと突き合わせて構造を再解決します。
downloadModules: false にするとオフライン向けに未取得モジュールのダウンロードを抑制します。開いた直後は未検証
(getStatus() は null)なので、必要に応じて validate() を呼びます。
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
console.log("base:", String(project.latest.getBase()?.key().id), project.latest.getBase()?.key().version.toString());
console.log("apps:", project.latest.getApplications().map((p) => String(p.key().id)));
console.log("locale:", project.latest.getLocale());
console.log("available modules:", project.latest.listAvailableModules().length, "/ all:", project.latest.listAllModules().length);
console.log("getStatus() before validate:", project.latest.getStatus()); // null
console.log("validate:", (await project.validate()).severity);
実行結果(実測)
base: jp.co.intra_mart.module.pack.im_basic 8.0.33
apps: [ 'jp.co.intra_mart.module.pack.im_theme_customize_pack' ]
locale: ja
available modules: 225 / all: 352
getStatus() before validate: null
validate: OK
注意点: open は呼ぶたびに新しいインスタンスを返します(セッション単一化はしません)。OpenProjectOptions は
{ signal?, downloadModules? }。
3. モジュールの選択
目的: プロジェクトの構成(どのモジュールを含めるか)を変更する。編集対象は project.structure(current)。
applyCheckState と selectWithDependencies は同期、unselectWithDependencies / unselectUsages /
resolveMissingModules は**非同期(Promise を返すため await 必須)**です。操作後に validate() で状態を確認します。
構造の探索 — ModuleStructure(project.latest / project.structure)の主なメソッド:
| メソッド | 返り値 | 用途 |
|---|---|---|
getBase() / getApplications() | Product | null / Product[] | ベース/アプリ |
listAvailableModules() | Module[] | 選択済み + 有効ユーザモジュール |
listAllModules() | Module[] | 全モジュール |
findModule(key) | Module | null | ModuleKey 完全一致 |
findModuleById(id) | Module[] | id 一致 |
各 Module(プロジェクト層)は key(): ModuleKey / getId() / getName() / getVersion() / isSelected() /
getConfigurations() を持ちます。
選択操作 — JugglingProject のメソッド:
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
// 対象モジュールを特定(ここではアプリの製品モジュール)
const target = project.latest.listAvailableModules().find((m) => m.getId().includes("im_theme_customize"))!;
console.log("target:", target.getId(), target.getVersion());
// チェックを外す(子孫へ伝播し、祖先 ModulePack/Product を再計算。同期)
project.applyCheckState(target, false);
const afterOff = await project.validate();
console.log("after uncheck:", afterOff.severity, afterOff.flatten().length);
// 不足モジュール(MV-01)を自動選択して解決
const resolved = await project.resolveMissingModules({ selectParents: true });
console.log("resolved:", resolved.selected.length, "unknown:", resolved.unknown.length);
// チェックを戻す
project.applyCheckState(target, true);
console.log("after recheck:", (await project.validate()).severity);
実行結果(実測)
target: jp.co.intra_mart.module.pack.im_theme_customize_pack 8.0.0
after uncheck: OK 0
resolved: 0 unknown: 0
after recheck: OK
上記のテーマアプリは他モジュールから依存されない任意アプリのため、外しても
NGになりません。必須依存を持つ モジュールを外すとmissing-moduleの finding が現れ、resolveMissingModules()で自動補完できます。
その他の選択操作:
| メソッド | 説明 |
|---|---|
selectWithDependencies(m) | 依存も含めて選択(同期) |
unselectWithDependencies(m) | 依存も含めて解除(非同期・await 必須。内部で snapshotFiles() を取得) |
unselectUsages(m) | そのモジュールを使う機能をすべて非選択(非同期・await 必須) |
computeCheckState(structure, modulePack) | 表示用の3値 "checked" | "unchecked" | "grayed"(@intra-mart/juggling-core/project。同期) |
補足: 各選択操作の後に
project.validate()を呼んで状態を確認するのが安全です。
4. ユーザモジュールの追加
目的: リポジトリに存在しない独自 .imm(ユーザ開発モジュール)をプロジェクトへ追加する。
手順: project.addUserModules(sourcePaths, opts?) に .imm の絶対パスの配列を渡します。追加された
.imm はプロジェクト直下の modules/ ディレクトリへコピーされ、juggling.im への反映は保存時に行われます。戻り値は
例外ではなく**判別 union AddUserModulesResult**です。
type AddUserModulesResult =
| { kind: "completed"; added: UserModule[] }
| { kind: "rejected"; finding: RuleFinding } // フェーズ1で検証違反 → 全体中止(未変更)
| { kind: "rolled-back"; added: UserModule[]; rejected: { path; messages }; notes }
| { kind: "aborted"; added: UserModule[]; error: JugglingError };
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
const result = await project.addUserModules(["/abs/path/to/my-module.imm"]);
switch (result.kind) {
case "completed": console.log("added:", result.added.length); break;
case "rejected": console.log("rejected:", result.finding.ruleId, result.finding.message); break;
case "rolled-back": console.log("rolled-back at:", result.rejected.path); break;
case "aborted": console.log("aborted:", result.error.message); break;
}
検証違反(存在するが不正な .imm)は rejected になります(実測):
addUserModules(不正ファイル) kind: rejected
finding: BLOCK UM-02 - 選択されたファイル(not-a-module.immはモジュールではありません。
削除・置換:
const userModules = project.structure.getUserModules();
await project.removeUserModule(userModules[0]!); // 削除
await project.replaceUserModule(userModules[0]!, "/abs/new.imm"); // 置換(remove → add)
5. 設定ファイルの配備
目的: モジュールが内包する設定ファイル(conf/*.xml)とスキーマ(schema/*.xsd)をプロジェクト直下へ展開する。
手順: 配備対象の Configuration は往復できないため、その都度ライブ構造から列挙します。各モジュールの
getConfigurations() から取得します。
import type { Configuration } from "@intra-mart/juggling-core/project";
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
// 現在の構成から Configuration を列挙
const configs: Configuration[] = [];
for (const module of project.structure.listAvailableModules()) {
for (const c of module.getConfigurations()) {
configs.push(c);
// プロパティ: c.file = 出力ファイル名, c.schema = xsd or null, c.require = "REQUIRE"|"OPTIONAL" / メソッド: c.getName()
}
}
console.log("configurations:", configs.length);
console.log("deployed?:", await project.isDeployedConfiguration(configs[0]!));
// 一括配備(戻り値は DeployBatchResult)
const result = await project.deployConfigurations(configs.slice(0, 3));
console.log("deployed:", result.deployed.length,
"| aborted:", result.aborted ? result.aborted.at.getName() : null,
"| notes:", result.notes.length);
// validate 結果の "missing-configuration" 対象だけをまとめて配備
const missing = await project.deployMissingConfigurations();
console.log("missing deployed:", missing.deployed.length);
実行結果(実測)
configurations: 217
deployed?: false
deployed: 3 | aborted: null | notes: 0
missing deployed: 0
DeployBatchResult = { deployed: Configuration[]; aborted: { at, remaining } | null; notes: { id }[] }。
注意点: 「スキーマが無く、かつ conf/<file> が既に存在」する設定に当たると、一括配備はその時点で残りごと打ち切り、
aborted に打ち切り位置と残数が入ります(既存ファイルを上書きしない安全側の挙動です)。個別 API は
deployConfiguration(c) / deploySchema(c)(スキーマのみ) / isDeployedConfiguration(c) / isDeployedSchema(c)。
6. war ファイルの生成
目的: 保存済みプロジェクトから war を生成する(プロジェクトは保存済みであることが前提。war 内に juggling.im を 埋め込むため)。
手順: ①テンプレート確認 → ②plan() で事前検証(ブロッカー・警告・ライセンス)→ ③exportWar() で生成。
im_ui などスクリプトを持つモジュールを含む場合は、外部スクリプトポート createImuiScriptPort の注入が必要です。
import { createImuiScriptPort } from "@intra-mart/juggling-core/build";
import { createNodeFileSystemPort } from "@intra-mart/juggling-core/runtime/node";
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
// ① テンプレート一覧
console.log(juggling.build.templates().map((t) => `${t.id}(${t.kind})`));
// war のビルド入力
const inputs = { licenseType: "product", environment: "product", includeSamples: false } as const;
// ② 事前検証(生成はしない)
const plan = await juggling.build.plan(project, { template: "resin40", inputs });
console.log("blockers:", plan.blockers.length, "| warnings:", plan.warnings.length,
"| licenses:", plan.licenses.products.length, "/", plan.licenses.others.length);
// plan.blockers が1件でもあると exportWar は BuildError を投げる
// ③ 生成(im_ui 用に外部スクリプトポートを注入)
const externalScripts = createImuiScriptPort({ fs: createNodeFileSystemPort() });
const result = await juggling.build.exportWar(project, {
template: "resin40", // resin40 / payara5 / weblogic12c / was80
inputs,
destDir: "./.work/build",
fileName: "imart", // 拡張子なし → imart.war
externalScripts,
onProgress: (e) => console.log("phase:", e.phase),
onLog: (e) => { if (e.level === "conflict") console.log("conflict:", e.message); },
// onMissingModule: "skip", // 既定 "throw"
});
console.log("artifactPath:", result.artifactPath);
console.log("entries:", result.entryNames.length, "| modules:", result.extractedModules.length, "| warnings:", result.warnings.length);
実行結果(実測。resin40 / product / サンプルなし)
[ 'weblogic12c(war)', 'payara5(war)', 'resin40(war)', 'was80(war)', 'static(static-zip)' ]
blockers: 0 | warnings: 1 | licenses: 0 / 23
phase: extractModuleBuilder
phase: WebXMLBuilder
phase: buildPropertiesBuilder_message
phase: CopyModifiedConfigratuinsBuilder
phase: ExternalScriptRunner
phase: ant.build
phase: builder_message
artifactPath: /…/.work/build/imart.war
entries: 20532 | modules: 224 | warnings: 1
生成された war は約 180 MB(20,532 エントリ)でした。plan.warnings の1件は storage-config の警告
(ストレージルートに /tmp 配下を指定している旨)で、ブロッカーではないため生成は続行されます。
WarBuildInputs = { licenseType: "product"|"trial"; environment: "ut"|"si"|"pt"|"product"; includeSamples: boolean }。
BuildResult = { artifactPath; extractedModules; warnings; entryNames }。ビルドフェーズは onProgress の phase で
上記7段階を通知します。
注意点: ライセンス同意ゲートはファサードが内部で処理します(juggling.build.exportWar を使う限り
acceptLicenses を意識する必要はありません)。AbortSignal で中断すると BuildError("build.aborted")。
7. 静的ファイルの生成
目的: PUBLIC リソースのみを集めた静的配信用 zip を生成する。
手順: テンプレート "static" を使い juggling.build.exportStatic()。war と異なり WEB-INF 生成物も外部スクリプトも
無く、PUBLIC 抽出 + zip 化のみです(出力は .zip)。
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
const inputs = { licenseType: "product", environment: "product", includeSamples: false } as const;
const result = await juggling.build.exportStatic(project, {
template: "static",
inputs, // StaticBuildInputs は WarBuildInputs と同形
destDir: "./.work/static",
fileName: "imart-static", // → imart-static.zip
onProgress: (e) => console.log("phase:", e.phase),
});
console.log("artifactPath:", result.artifactPath);
console.log("entries:", result.entryNames.length, "| modules:", result.extractedModules.length);
実行結果(実測)
phase: extractModuleBuilder
phase: ant.build
phase: builder_message
artifactPath: /…/.work/static/imart-static.zip
entries: 7144 | modules: 225
生成された zip は約 45 MB(7,144 エントリ。alert/*.html などの PUBLIC リソース)。フェーズは war の7段階に対して
3段階です(war にあって static に無いのは WebXMLBuilder / buildPropertiesBuilder_message /
CopyModifiedConfigratuinsBuilder / ExternalScriptRunner の4フェーズ)。
8. パッチの検索と適用
目的: 現在の構成に対して適用可能なパッチを検索し、適用する。
手順: project.findPatches() で構成全体を走査し、PatchSearchResult を得ます。適用は
project.applyPatches(patches)。適用は「複製に試適用 → 依存範囲検証(dry-run)→ OK なら本適用」の順で行われ、
NG のときは本体を変更せず applied:false を返します。
import { ModuleKey } from "@intra-mart/juggling-core/model";
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
// ① 検索
const found = await project.findPatches(); // PatchSearchResult { candidates, all(), notes }
console.log("candidates:", found.candidates.length);
for (const c of found.candidates.slice(0, 3)) {
// c.target: ModuleKey(適用先) / c.patch: Module(適用するパッチ)
console.log(String(c.target.id), c.target.version.toString(), "->", c.patch.key().version.toString());
}
// ② 適用(空配列は ArgumentError。1件以上必須)
if (found.candidates.length > 0) {
const result = await project.applyPatches(found.all()); // ApplyPatchesResult
if (result.applied) console.log("applied");
else console.log("not applied:", result.reason); // "dependency-validation"
}
// 単発のパッチ照会(そのモジュールの最新パッチ。無ければ null)
const single = await juggling.catalog.findPatch(ModuleKey.of("jp.co.intra_mart.module.pack.im_basic", "8.0.33"));
console.log("latest patch:", single ? single.version.toString() : null);
実行結果(実測)
candidates: 30
jp.co.intra_mart.im_workflow 8.0.33 -> 8.0.33-PATCH_004
jp.co.intra_mart.im_portal 8.0.26 -> 8.0.26-PATCH_001
jp.co.intra_mart.imbox_smartphone 8.0.20 -> 8.0.20-PATCH_001
applied
latest patch: null
applyPatches([])(空配列)は ArgumentError: applyPatches requires at least one patch candidate(実測)。
なお catalog.findPatch はベース製品パック(im_basic)自体のパッチは持たないため null を返します(パッチは
im_workflow などの個別モジュールに存在します)。
ApplyPatchesResult = { applied: true; notes } | { applied: false; reason: "dependency-validation"; validation }。
注意点: 検索途中で例外が起きると、以降の収集を放棄して収集済みだけを返します。ユーザモジュールは
パッチ検索対象外です。個別適用は found.candidates の部分列を applyPatches に渡します。適用後の状態を残すには
project.save() が必要です。
9. バージョンアップ
目的: ベース製品を新しいバージョンへ更新する(plan → apply の2段階)。
手順: ①更新可能なベース候補一覧 → ②アプリの更新状態確認 → ③planUpgrade() で差分・検証をまとめて取得 →
④ブロッカーが無ければ applyUpgrade() で適用。
const project = await juggling.projects.open("./.work/proj1", { downloadModules: false });
console.log("current base:", project.latest.getBase()?.key().version.toString());
// ① ベース更新候補(UpgradeBaseCandidate { category, module, isCurrent })
const candidates = await project.listUpgradeBaseCandidates();
for (const c of candidates) console.log(String(c.module.id), c.module.version.toString(), "isCurrent:", c.isCurrent);
// 現在より新しい最大バージョンを対象にする
const target = [...candidates]
.filter((c) => String(c.module.id) === "jp.co.intra_mart.module.pack.im_basic" && !c.isCurrent)
.sort((a, b) => b.module.version.compareTo(a.module.version))[0]!;
// ② 各アプリの更新状態(ApplicationUpdateStatusEntry { application, state, candidates })
const appStatuses = await project.getApplicationUpdateStatuses(target.module);
for (const s of appStatuses) console.log(String(s.application.key().id), "state:", s.state, "candidates:", s.candidates.length);
// ③ plan(検証 + 差分プレビュー)
const plan = await project.planUpgrade({ base: target.module, mode: "automatic" }); // "automatic" | "individual"
console.log("blockers:", plan.blockers.length, "| diff:", plan.diff.length,
"| requiredUpdates:", plan.requiredUpdates.length,
"| updateModuleCandidates:", plan.updateModuleCandidates.length,
"| impossibleApplications:", plan.impossibleApplications.length);
for (const d of plan.diff.slice(0, 3)) console.log(d.kind, d.module.getId(), d.module.getVersion(), "<-", d.oldVersion?.toString() ?? "");
// ④ 適用(ブロッカーが無いとき)
if (plan.blockers.length === 0) {
const result = await project.applyUpgrade(plan); // ApplyUpgradeResult
console.log("applied:", result.applied);
console.log("new base:", project.structure.getBase()?.key().version.toString());
console.log("validate:", (await project.validate()).severity);
}
実行結果(実測。im_basic 8.0.33 → 8.0.39)
current base: 8.0.33
jp.co.intra_mart.module.pack.im_basic 8.0.33 isCurrent: true
jp.co.intra_mart.module.pack.im_basic 8.0.34 isCurrent: false
… (8.0.35 … 8.0.39)
app update statuses: 1 (im_theme_customize_pack state: NONE candidates: 23)
blockers: 0 | diff: 128 | requiredUpdates: 0 | updateModuleCandidates: 0 | impossibleApplications: 0
applied: true
new base: 8.0.39
validate: NG
上記では適用後の
validate()がNG(1件)になりました。ベースを 8.0.39 に上げた結果、旧バージョンのままの テーマアプリ(8.0.0)が構成上の不整合を生じたためです。バージョンアップ後はvalidate()を確認し、必要に応じて アプリ側の更新(updateModuleCandidates/individualモード)やresolveMissingModules()を行います。
注意点:
planは同一プロジェクトから生成したものでなければapplyUpgradeはArgumentError。mode: "individual"の場合はapplyUpgrade(plan, { updateModules })で個別モジュールを指定します。ApplyUpgradeResult={ applied: true; autoSelected; unresolvedMissing; postUpdateGuidanceUrl; notes } | { applied: false; reason: "dependency-validation" | "repair-not-converged"; validation }。- 適用後の状態を残すには
project.save()。
10. 設定の変更・リモートリポジトリの変更
目的: モジュールリポジトリ(取得元)の設定を確認・変更する。
手順: juggling.repositories(RepositoryManager)で設定を扱います。location が http/https ならリモート、
それ以外はローカル(FILESET)パスとして扱われます。
// 現在の設定を確認
const settings = await juggling.repositories.getSettings(); // RepositorySettingsEntry[]
for (const e of settings) {
console.log(e.sort, e.name, "|", e.location, "| erasable:", e.erasable, "| available:", e.available);
}
// 出荷時の既定に戻す値を取得
const defaults = await juggling.repositories.systemDefaultSettings();
console.log("managed path:", juggling.repositories.getManagedRepositoryPath());
実行結果(実測)
1 intra-mart Remote Repository | http://repository.intra-mart.jp/base | erasable: true | available: true
3 intra-mart Application Repository | http://repository.intra-mart.jp/app | erasable: true | available: true
managed path: /…/.managed
既定のリポジトリは base / app の2件です。
RepositorySettingsEntry = { name; location; sort; description; erasable; available }。sort の昇順が探索優先順、
erasable: false は編集/削除不可、available: false は setup 時に登録スキップ。
リポジトリを変更する2つの方法:
-
初期化時に指定(推奨・最優先) —
createJuggling({ repositories })で丸ごと差し替えます。ローカル FILESET を 使う場合はlocationにディレクトリパスを渡します。const juggling = await createJuggling({managedRepositoryPath: "./.managed",repositories: [{ name: "base", location: "http://repository.intra-mart.jp/base", sort: 0, description: "", erasable: true, available: true },{ name: "app", location: "http://repository.intra-mart.jp/app", sort: 1, description: "", erasable: true, available: true },// ローカルの場合: { name: "local", location: "/path/to/fileset-repo", sort: 2, description: "", erasable: true, available: true },],}); -
実行時に永続変更 —
saveSettings(entries)で設定を保存します。ただし保存は永続ストア(KVS)への反映のみで、 実際に登録リポジトリへ反映するにはsetup()の再実行(または新しいcreateJuggling)が必要です。const entries = await juggling.repositories.getSettings();entries.push({ name: "extra", location: "http://example/app2", sort: 9, description: "", erasable: true, available: true });await juggling.repositories.saveSettings(entries);await juggling.repositories.setup(); // 反映
保守系メソッド: cleanManagedRepository()(ローカルキャッシュ全消去)、clearHttpCache()(HTTP キャッシュ削除)、
systemDefaultSettings()(既定へ戻す値)。
注意点: saveSettings の検証は、erasable: false のエントリの削除・無効化・内容変更を JacklingArgumentError で
拒否します(sort の変更=並べ替えは許容)。ただし既定リポジトリ2件(base/app)は erasable: true
(=編集・削除可能)です。削除から保護されるのは、管理リポジトリ(getManagedRepository())など erasable: false の
エントリに限られます。
付録 A. 集約ファサード早見表
createJuggling(options?) : Promise<Juggling>
└─ juggling
├─ catalog
│ getBaseCategories() / getApplicationCategories() / getRecommendedData()
│ findPatch(key)
├─ projects
│ create(req) / createFromRecommended(req) / open(dir, opts?) → JugglingProject
├─ build
│ templates() / findTemplate(id)
│ plan(project, {template, inputs}) → BuildPlan
│ exportWar(project, opts) / exportStatic(project, opts) → BuildResult
├─ repositories getSettings() / saveSettings() / systemDefaultSettings() / setup()
│ cleanManagedRepository() / clearHttpCache() / close()
├─ repositoryService / moduleService / rules / context
JugglingProject(projects.create/open の戻り)
dir / structure / latest / history
validate() / isModified() / planSave() / save(opts?) / compact()
applyCheckState() / selectWithDependencies() / unselectWithDependencies()
unselectUsages() / resolveMissingModules(opts?)
addUserModules() / removeUserModule() / replaceUserModule()
deployConfigurations() / deployMissingConfigurations() / deploySchema()
isDeployedConfiguration() / isDeployedSchema()
findPatches() / applyPatches()
listUpgradeBaseCandidates() / getApplicationUpdateStatuses() / planUpgrade() / applyUpgrade()
listAdditionalResourcePlatforms() / deployAdditionalResources()
付録 B. 「戻り値で表現される業務的失敗」一覧
| 操作 | 戻り値の型 | 失敗の表し方 |
|---|---|---|
validate() | AggregateStatus | severity === "NG"(例外ではない) |
save() | SaveResult | status.severity(保存自体は成功) |
addUserModules() | AddUserModulesResult | kind: "rejected"/"rolled-back"/"aborted" |
deployConfigurations() | DeployBatchResult | aborted !== null(既存ファイル保護による打ち切り) |
applyPatches() | ApplyPatchesResult | applied: false, reason: "dependency-validation" |
applyUpgrade() | ApplyUpgradeResult | applied: false, reason: "dependency-validation"/"repair-not-converged" |
一方、ArgumentError(空配列・必須未指定)、BuildError(no-base / template 不明 / aborted)、JugglingError
(I/O)などは例外として throw されます。
本マニュアルは @intra-mart/juggling-core@0.1.2 を Node.js 22.19.0 上で http://repository.intra-mart.jp/ に
接続して全パターンを実行し、掲載コードを型検査(tsc)したうえで作成しています。