oh-my-customcode 1.1.101 → 1.1.103
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/cli/index.js +518 -53
- package/dist/index.js +135 -6
- package/package.json +1 -1
- package/templates/.claude/output-styles/korean-engineer.md +1 -1
- package/templates/.claude/rules/MUST-agent-design.md +2 -2
- package/templates/.claude/rules/MUST-agent-teams.md +13 -2
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +41 -29
- package/templates/.claude/rules/MUST-permissions.md +12 -9
- package/templates/.claude/skills/action-validator/SKILL.md +2 -2
- package/templates/.claude/skills/adaptive-harness/SKILL.md +2 -2
- package/templates/.claude/skills/adversarial-review/SKILL.md +1 -1
- package/templates/.claude/skills/agent-eval-framework/SKILL.md +2 -2
- package/templates/.claude/skills/de-lead-routing/SKILL.md +2 -2
- package/templates/.claude/skills/deep-plan/SKILL.md +4 -4
- package/templates/.claude/skills/deep-verify/SKILL.md +3 -3
- package/templates/.claude/skills/design-shotgun/SKILL.md +1 -1
- package/templates/.claude/skills/dev-lead-routing/SKILL.md +2 -2
- package/templates/.claude/skills/dev-review/SKILL.md +2 -2
- package/templates/.claude/skills/evaluator-optimizer/SKILL.md +1 -1
- package/templates/.claude/skills/fsd/SKILL.md +3 -4
- package/templates/.claude/skills/harness-eval/SKILL.md +1 -1
- package/templates/.claude/skills/harness-synthesizer/SKILL.md +1 -1
- package/templates/.claude/skills/homework/SKILL.md +3 -3
- package/templates/.claude/skills/idea/SKILL.md +4 -2
- package/templates/.claude/skills/instinct-extractor/SKILL.md +8 -8
- package/templates/.claude/skills/intent-detection/SKILL.md +1 -1
- package/templates/.claude/skills/multi-model-verification/SKILL.md +2 -2
- package/templates/.claude/skills/omcustom-release-notes/SKILL.md +1 -1
- package/templates/.claude/skills/pipeline/SKILL.md +1 -1
- package/templates/.claude/skills/pipeline/workflows/auto-dev.yaml +26 -3
- package/templates/.claude/skills/post-release-followup/SKILL.md +2 -2
- package/templates/.claude/skills/professor-triage/SKILL.md +10 -10
- package/templates/.claude/skills/profile/SKILL.md +4 -4
- package/templates/.claude/skills/qa-lead-routing/SKILL.md +2 -2
- package/templates/.claude/skills/release-plan/SKILL.md +1 -1
- package/templates/.claude/skills/research/SKILL.md +8 -6
- package/templates/.claude/skills/result-aggregation/SKILL.md +1 -1
- package/templates/.claude/skills/roundtable-debate/SKILL.md +2 -2
- package/templates/.claude/skills/scout/SKILL.md +3 -8
- package/templates/.claude/skills/sec-agentshield-wrapper/SKILL.md +1 -1
- package/templates/.claude/skills/secretary-routing/SKILL.md +2 -2
- package/templates/.claude/skills/skill-extractor/SKILL.md +1 -1
- package/templates/.claude/skills/structured-dev-cycle/SKILL.md +1 -1
- package/templates/.claude/skills/task-decomposition/SKILL.md +1 -1
- package/templates/.claude/skills/worker-reviewer-pipeline/SKILL.md +5 -1
- package/templates/guides/claude-code/15-version-compatibility.md +149 -2
- package/templates/guides/deep-plan/phases.md +11 -9
- package/templates/guides/harness-engineering/README.md +2 -1
- package/templates/guides/professor-triage/README.md +1 -1
- package/templates/guides/professor-triage/phases.md +1 -1
- package/templates/manifest.json +1 -1
- package/templates/workflows/auto-dev.yaml +26 -3
package/dist/cli/index.js
CHANGED
|
@@ -335,7 +335,7 @@ var init_package = __esm(() => {
|
|
|
335
335
|
workspaces: [
|
|
336
336
|
"packages/*"
|
|
337
337
|
],
|
|
338
|
-
version: "1.1.
|
|
338
|
+
version: "1.1.103",
|
|
339
339
|
description: "Batteries-included agent harness for Claude Code",
|
|
340
340
|
type: "module",
|
|
341
341
|
bin: {
|
|
@@ -7331,7 +7331,7 @@ var require_public_api = __commonJS(function(exports) {
|
|
|
7331
7331
|
});
|
|
7332
7332
|
|
|
7333
7333
|
// src/utils/fs.ts
|
|
7334
|
-
import { dirname as dirname3, isAbsolute as
|
|
7334
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4, relative, resolve as resolve2, sep as sep3 } from "node:path";
|
|
7335
7335
|
import { fileURLToPath } from "node:url";
|
|
7336
7336
|
function validatePreserveFilePath(filePath, projectRoot) {
|
|
7337
7337
|
if (!filePath || filePath.trim() === "") {
|
|
@@ -7340,7 +7340,7 @@ function validatePreserveFilePath(filePath, projectRoot) {
|
|
|
7340
7340
|
reason: "Path cannot be empty"
|
|
7341
7341
|
};
|
|
7342
7342
|
}
|
|
7343
|
-
if (
|
|
7343
|
+
if (isAbsolute3(filePath)) {
|
|
7344
7344
|
return {
|
|
7345
7345
|
valid: false,
|
|
7346
7346
|
reason: "Absolute paths are not allowed"
|
|
@@ -7348,7 +7348,7 @@ function validatePreserveFilePath(filePath, projectRoot) {
|
|
|
7348
7348
|
}
|
|
7349
7349
|
const resolvedPath = resolve2(projectRoot, filePath);
|
|
7350
7350
|
const relativePath = relative(projectRoot, resolvedPath);
|
|
7351
|
-
if (relativePath.startsWith("..") ||
|
|
7351
|
+
if (relativePath.startsWith("..") || isAbsolute3(relativePath)) {
|
|
7352
7352
|
return {
|
|
7353
7353
|
valid: false,
|
|
7354
7354
|
reason: "Path cannot traverse outside project root"
|
|
@@ -11972,8 +11972,7 @@ init_registry();
|
|
|
11972
11972
|
init_package();
|
|
11973
11973
|
import { execSync as execSync2, spawnSync } from "node:child_process";
|
|
11974
11974
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11975
|
-
import {
|
|
11976
|
-
import { dirname as dirname2, join as join3 } from "node:path";
|
|
11975
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3 } from "node:path";
|
|
11977
11976
|
import { createInterface } from "node:readline/promises";
|
|
11978
11977
|
|
|
11979
11978
|
// node_modules/.bun/i18next@26.0.2+ff0372d108591aba/node_modules/i18next/dist/esm/i18next.js
|
|
@@ -14665,7 +14664,9 @@ var en_default = {
|
|
|
14665
14664
|
openOption: "Open browser automatically after start",
|
|
14666
14665
|
foregroundOption: "Run in foreground (not detached)",
|
|
14667
14666
|
started: "Web UI started: http://localhost:{{port}}",
|
|
14668
|
-
failed: "Failed to start Web UI server"
|
|
14667
|
+
failed: "Failed to start Web UI server",
|
|
14668
|
+
homeUnresolved: "Cannot start the Web UI server: the home directory cannot be resolved, either because HOME is a relative path or because HOME, the OS-reported home and the passwd entry are all empty, so the PID file location cannot be determined. Set HOME to an absolute path.",
|
|
14669
|
+
pidNotWritable: "Cannot start the Web UI server: the PID file {{path}} cannot be written ({{error}}). Check the PID file path and its parent directory."
|
|
14669
14670
|
},
|
|
14670
14671
|
stop: {
|
|
14671
14672
|
description: "Stop the Web UI server",
|
|
@@ -15083,7 +15084,9 @@ var ko_default = {
|
|
|
15083
15084
|
openOption: "시작 후 브라우저 자동 열기",
|
|
15084
15085
|
foregroundOption: "포그라운드 실행 (백그라운드 분리 안 함)",
|
|
15085
15086
|
started: "Web UI 시작됨: http://localhost:{{port}}",
|
|
15086
|
-
failed: "Web UI 서버 시작 실패"
|
|
15087
|
+
failed: "Web UI 서버 시작 실패",
|
|
15088
|
+
homeUnresolved: "Web UI 서버를 시작할 수 없습니다: 홈 디렉토리를 확인할 수 없어 PID 파일 위치를 정할 수 없습니다. HOME이 상대 경로이거나, HOME·OS 보고 홈·passwd 항목이 모두 비어 있는 경우입니다. HOME을 절대 경로로 설정하세요.",
|
|
15089
|
+
pidNotWritable: "Web UI 서버를 시작할 수 없습니다: PID 파일 {{path}}에 쓸 수 없습니다 ({{error}}). PID 파일 경로와 그 상위 디렉토리를 확인하세요."
|
|
15087
15090
|
},
|
|
15088
15091
|
stop: {
|
|
15089
15092
|
description: "Web UI 서버 중지",
|
|
@@ -15269,9 +15272,18 @@ var i18n = {
|
|
|
15269
15272
|
};
|
|
15270
15273
|
|
|
15271
15274
|
// src/core/self-update.ts
|
|
15275
|
+
init_home();
|
|
15272
15276
|
var DEFAULT_PACKAGE_NAME = "oh-my-customcode";
|
|
15273
15277
|
var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
15274
|
-
var
|
|
15278
|
+
var CACHE_DIR_NAME = ".oh-my-customcode";
|
|
15279
|
+
var CACHE_FILE_NAME = "self-update-cache.json";
|
|
15280
|
+
function resolveSelfUpdateCachePath(sources) {
|
|
15281
|
+
const home = resolveHomeDir(sources);
|
|
15282
|
+
if (!isAbsolute2(home)) {
|
|
15283
|
+
return null;
|
|
15284
|
+
}
|
|
15285
|
+
return join3(home, CACHE_DIR_NAME, CACHE_FILE_NAME);
|
|
15286
|
+
}
|
|
15275
15287
|
var MS_EPOCH_THRESHOLD = 1000000000000;
|
|
15276
15288
|
function normalizeVersion(version) {
|
|
15277
15289
|
return version.trim().replace(/^v/i, "").split("-")[0] || "";
|
|
@@ -15367,20 +15379,36 @@ function readSelfUpdateCache(cachePath) {
|
|
|
15367
15379
|
}
|
|
15368
15380
|
return null;
|
|
15369
15381
|
}
|
|
15382
|
+
function readCacheAt(cachePath) {
|
|
15383
|
+
return cachePath === null ? null : readSelfUpdateCache(cachePath);
|
|
15384
|
+
}
|
|
15385
|
+
function isFileSystemError(error) {
|
|
15386
|
+
const { code, errno } = error ?? {};
|
|
15387
|
+
return typeof code === "string" && typeof errno === "number";
|
|
15388
|
+
}
|
|
15370
15389
|
function writeCache(cachePath, latestVersion, now) {
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
mkdirSync(dir, { recursive: true });
|
|
15390
|
+
if (cachePath === null) {
|
|
15391
|
+
return;
|
|
15374
15392
|
}
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15393
|
+
try {
|
|
15394
|
+
const dir = dirname2(cachePath);
|
|
15395
|
+
if (!existsSync(dir)) {
|
|
15396
|
+
mkdirSync(dir, { recursive: true });
|
|
15397
|
+
}
|
|
15398
|
+
const payload = {
|
|
15399
|
+
checkedAt: new Date(now).toISOString(),
|
|
15400
|
+
latestVersion,
|
|
15401
|
+
version: latestVersion,
|
|
15402
|
+
timestamp: Math.floor(now / 1000),
|
|
15403
|
+
source: "omcustom-cli"
|
|
15404
|
+
};
|
|
15405
|
+
writeFileSync(cachePath, `${JSON.stringify(payload, null, 2)}
|
|
15383
15406
|
`, "utf-8");
|
|
15407
|
+
} catch (error) {
|
|
15408
|
+
if (!isFileSystemError(error)) {
|
|
15409
|
+
throw error;
|
|
15410
|
+
}
|
|
15411
|
+
}
|
|
15384
15412
|
}
|
|
15385
15413
|
function isCacheFresh(cache, now, cacheTtlMs) {
|
|
15386
15414
|
const checkedAt = new Date(cache.checkedAt).getTime();
|
|
@@ -15520,7 +15548,7 @@ function executeSelfUpdate(options = {}) {
|
|
|
15520
15548
|
}
|
|
15521
15549
|
function checkSelfUpdate(options) {
|
|
15522
15550
|
const packageName = options.packageName || DEFAULT_PACKAGE_NAME;
|
|
15523
|
-
const cachePath = options.cachePath ||
|
|
15551
|
+
const cachePath = options.cachePath || resolveSelfUpdateCachePath();
|
|
15524
15552
|
const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
15525
15553
|
const fetchLatestVersion = options.fetchLatestVersion || fetchLatestVersionFromNpm;
|
|
15526
15554
|
const now = options.now ?? Date.now();
|
|
@@ -15536,7 +15564,7 @@ function checkSelfUpdate(options) {
|
|
|
15536
15564
|
}
|
|
15537
15565
|
let latestVersion = null;
|
|
15538
15566
|
let usedCache = false;
|
|
15539
|
-
const cache =
|
|
15567
|
+
const cache = readCacheAt(cachePath);
|
|
15540
15568
|
if (cache && isCacheFresh(cache, now, cacheTtlMs)) {
|
|
15541
15569
|
const cachedVersion = normalizeVersion(cache.latestVersion);
|
|
15542
15570
|
if (isVersionPlausible(currentVersion, cachedVersion)) {
|
|
@@ -15758,6 +15786,7 @@ var MESSAGES = {
|
|
|
15758
15786
|
"lockfile.component_dir_missing": "Component directory missing: {{path}}",
|
|
15759
15787
|
"lockfile.hash_failed": "Failed to hash file: {{path}} — {{error}}",
|
|
15760
15788
|
"lockfile.entry_added": "Lockfile entry added: {{path}} ({{component}})",
|
|
15789
|
+
"lockfile.entry_git_excluded": "Lockfile entry skipped (not in git listing): {{path}}",
|
|
15761
15790
|
"update.start": "Checking for updates...",
|
|
15762
15791
|
"update.success": "Updated from {{from}} to {{to}}",
|
|
15763
15792
|
"update.components_synced": "Components synced (version {{version}}): {{components}}",
|
|
@@ -15879,6 +15908,7 @@ var MESSAGES = {
|
|
|
15879
15908
|
"lockfile.component_dir_missing": "컴포넌트 디렉토리 없음: {{path}}",
|
|
15880
15909
|
"lockfile.hash_failed": "파일 해시 실패: {{path}} — {{error}}",
|
|
15881
15910
|
"lockfile.entry_added": "잠금 파일 항목 추가: {{path}} ({{component}})",
|
|
15911
|
+
"lockfile.entry_git_excluded": "잠금 파일 항목 제외 (git 목록에 없음): {{path}}",
|
|
15882
15912
|
"update.start": "업데이트 확인 중...",
|
|
15883
15913
|
"update.success": "{{from}}에서 {{to}}로 업데이트 완료",
|
|
15884
15914
|
"update.components_synced": "컴포넌트 동기화 완료 (버전 {{version}}): {{components}}",
|
|
@@ -16334,10 +16364,12 @@ function getComponentPath(component) {
|
|
|
16334
16364
|
|
|
16335
16365
|
// src/core/lockfile.ts
|
|
16336
16366
|
init_fs();
|
|
16367
|
+
import { execFile } from "node:child_process";
|
|
16337
16368
|
import { createHash } from "node:crypto";
|
|
16338
16369
|
import { createReadStream } from "node:fs";
|
|
16339
|
-
import { readdir, stat } from "node:fs/promises";
|
|
16370
|
+
import { readdir, realpath, stat } from "node:fs/promises";
|
|
16340
16371
|
import { join as join7, relative as relative2 } from "node:path";
|
|
16372
|
+
import { promisify } from "node:util";
|
|
16341
16373
|
var LOCKFILE_NAME = ".omcustom.lock.json";
|
|
16342
16374
|
var LOCKFILE_VERSION = 1;
|
|
16343
16375
|
var LOCKFILE_COMPONENTS = [
|
|
@@ -16350,6 +16382,87 @@ var LOCKFILE_COMPONENTS = [
|
|
|
16350
16382
|
"guides"
|
|
16351
16383
|
];
|
|
16352
16384
|
var COMPONENT_PATHS = LOCKFILE_COMPONENTS.map((component) => [getComponentPath(component), component]);
|
|
16385
|
+
var execFileAsync = promisify(execFile);
|
|
16386
|
+
var GIT_LOCATION_ENV_KEYS = [
|
|
16387
|
+
"GIT_DIR",
|
|
16388
|
+
"GIT_WORK_TREE",
|
|
16389
|
+
"GIT_INDEX_FILE",
|
|
16390
|
+
"GIT_PREFIX",
|
|
16391
|
+
"GIT_COMMON_DIR"
|
|
16392
|
+
];
|
|
16393
|
+
var GIT_INJECTED_CONFIG_ENV_KEYS = ["GIT_CONFIG_PARAMETERS", "GIT_CONFIG_COUNT"];
|
|
16394
|
+
var GIT_INJECTED_CONFIG_ENV_PREFIXES = ["GIT_CONFIG_KEY_", "GIT_CONFIG_VALUE_"];
|
|
16395
|
+
var GIT_REQUIRED_HINT = "build from a git checkout: excluding ignored local files from the lockfile requires git (#1819)";
|
|
16396
|
+
function gitChildEnv() {
|
|
16397
|
+
const env = { ...process.env };
|
|
16398
|
+
for (const key of [...GIT_LOCATION_ENV_KEYS, ...GIT_INJECTED_CONFIG_ENV_KEYS]) {
|
|
16399
|
+
delete env[key];
|
|
16400
|
+
}
|
|
16401
|
+
for (const key of Object.keys(env)) {
|
|
16402
|
+
if (GIT_INJECTED_CONFIG_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
|
|
16403
|
+
delete env[key];
|
|
16404
|
+
}
|
|
16405
|
+
}
|
|
16406
|
+
return env;
|
|
16407
|
+
}
|
|
16408
|
+
async function runGit(targetDir, args, env) {
|
|
16409
|
+
try {
|
|
16410
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
16411
|
+
cwd: targetDir,
|
|
16412
|
+
env,
|
|
16413
|
+
encoding: "utf-8",
|
|
16414
|
+
maxBuffer: 256 * 1024 * 1024
|
|
16415
|
+
});
|
|
16416
|
+
return stdout;
|
|
16417
|
+
} catch (err) {
|
|
16418
|
+
const detail = err instanceof Error ? err.message.trim() : String(err);
|
|
16419
|
+
throw new Error(`git ${args[0]} failed in ${targetDir}: ${detail} — ${GIT_REQUIRED_HINT}`);
|
|
16420
|
+
}
|
|
16421
|
+
}
|
|
16422
|
+
function foldCase(nfcPath) {
|
|
16423
|
+
return nfcPath.toLowerCase();
|
|
16424
|
+
}
|
|
16425
|
+
function isGitVisible(visible, relativePath) {
|
|
16426
|
+
if (visible.exact.has(relativePath)) {
|
|
16427
|
+
return true;
|
|
16428
|
+
}
|
|
16429
|
+
const nfc = relativePath.normalize("NFC");
|
|
16430
|
+
if (visible.nfc.has(nfc)) {
|
|
16431
|
+
return true;
|
|
16432
|
+
}
|
|
16433
|
+
return visible.folded?.has(foldCase(nfc)) ?? false;
|
|
16434
|
+
}
|
|
16435
|
+
function stripFinalNewline(output) {
|
|
16436
|
+
return output.endsWith(`
|
|
16437
|
+
`) ? output.slice(0, -1) : output;
|
|
16438
|
+
}
|
|
16439
|
+
async function assertGitWorkTreeRoot(targetDir, env) {
|
|
16440
|
+
const topLevel = stripFinalNewline(await runGit(targetDir, ["rev-parse", "--show-toplevel"], env));
|
|
16441
|
+
let realTop;
|
|
16442
|
+
let realTarget;
|
|
16443
|
+
try {
|
|
16444
|
+
[realTop, realTarget] = await Promise.all([realpath(topLevel), realpath(targetDir)]);
|
|
16445
|
+
} catch (err) {
|
|
16446
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
16447
|
+
throw new Error(`cannot resolve the git work tree root of ${targetDir}: ${detail} — ${GIT_REQUIRED_HINT}`);
|
|
16448
|
+
}
|
|
16449
|
+
if (realTop !== realTarget) {
|
|
16450
|
+
throw new Error(`${targetDir} is not the root of its git work tree (root: ${topLevel}) — ${GIT_REQUIRED_HINT}`);
|
|
16451
|
+
}
|
|
16452
|
+
}
|
|
16453
|
+
async function listGitVisiblePaths(targetDir) {
|
|
16454
|
+
const env = gitChildEnv();
|
|
16455
|
+
await assertGitWorkTreeRoot(targetDir, env);
|
|
16456
|
+
const pathspecs = COMPONENT_PATHS.map(([prefix]) => prefix);
|
|
16457
|
+
const [listing, ignoreCase] = await Promise.all([
|
|
16458
|
+
runGit(targetDir, ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...pathspecs], env),
|
|
16459
|
+
runGit(targetDir, ["config", "--type=bool", "--default=false", "--get", "core.ignorecase"], env)
|
|
16460
|
+
]);
|
|
16461
|
+
const exact = new Set(listing.split("\x00").filter((entry) => entry.length > 0));
|
|
16462
|
+
const nfc = new Set([...exact].map((entry) => entry.normalize("NFC")));
|
|
16463
|
+
const folded = stripFinalNewline(ignoreCase) === "true" ? new Set([...nfc].map(foldCase)) : null;
|
|
16464
|
+
return { exact, nfc, folded };
|
|
16465
|
+
}
|
|
16353
16466
|
function computeFileHash(filePath) {
|
|
16354
16467
|
return new Promise((resolve, reject) => {
|
|
16355
16468
|
const hash = createHash("sha256");
|
|
@@ -16431,8 +16544,13 @@ async function collectFiles(dir, projectRoot, isTopLevel) {
|
|
|
16431
16544
|
}
|
|
16432
16545
|
return results;
|
|
16433
16546
|
}
|
|
16434
|
-
|
|
16547
|
+
function sortFilesByPath(files) {
|
|
16548
|
+
const sorted = Object.keys(files).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
16549
|
+
return Object.fromEntries(sorted.map((key) => [key, files[key]]));
|
|
16550
|
+
}
|
|
16551
|
+
async function generateLockfile(targetDir, generatorVersion, templateVersion, options = {}) {
|
|
16435
16552
|
const files = {};
|
|
16553
|
+
const gitVisible = options.excludeGitIgnored ? await listGitVisiblePaths(targetDir) : null;
|
|
16436
16554
|
const componentRoots = COMPONENT_PATHS.map(([prefix]) => join7(targetDir, prefix));
|
|
16437
16555
|
for (const componentRoot of componentRoots) {
|
|
16438
16556
|
const exists = await fileExists(componentRoot);
|
|
@@ -16443,6 +16561,10 @@ async function generateLockfile(targetDir, generatorVersion, templateVersion) {
|
|
|
16443
16561
|
const allFiles = await collectFiles(componentRoot, targetDir, false);
|
|
16444
16562
|
for (const absolutePath of allFiles) {
|
|
16445
16563
|
const relativePath = relative2(targetDir, absolutePath).replace(/\\/g, "/");
|
|
16564
|
+
if (gitVisible !== null && !isGitVisible(gitVisible, relativePath)) {
|
|
16565
|
+
debug("lockfile.entry_git_excluded", { path: relativePath });
|
|
16566
|
+
continue;
|
|
16567
|
+
}
|
|
16446
16568
|
let hash;
|
|
16447
16569
|
let size;
|
|
16448
16570
|
try {
|
|
@@ -16467,15 +16589,50 @@ async function generateLockfile(targetDir, generatorVersion, templateVersion) {
|
|
|
16467
16589
|
generatorVersion,
|
|
16468
16590
|
generatedAt: new Date().toISOString(),
|
|
16469
16591
|
templateVersion,
|
|
16470
|
-
files
|
|
16592
|
+
files: sortFilesByPath(files)
|
|
16471
16593
|
};
|
|
16472
16594
|
}
|
|
16473
|
-
|
|
16595
|
+
function hasSameLockfileContent(next, previous) {
|
|
16596
|
+
if (next.lockfileVersion !== previous.lockfileVersion || next.generatorVersion !== previous.generatorVersion || next.templateVersion !== previous.templateVersion) {
|
|
16597
|
+
return false;
|
|
16598
|
+
}
|
|
16599
|
+
const nextKeys = Object.keys(next.files);
|
|
16600
|
+
if (nextKeys.length !== Object.keys(previous.files).length) {
|
|
16601
|
+
return false;
|
|
16602
|
+
}
|
|
16603
|
+
return nextKeys.every((key) => {
|
|
16604
|
+
if (!Object.hasOwn(previous.files, key)) {
|
|
16605
|
+
return false;
|
|
16606
|
+
}
|
|
16607
|
+
const before = previous.files[key];
|
|
16608
|
+
const after = next.files[key];
|
|
16609
|
+
return typeof before === "object" && before !== null && before.templateHash === after.templateHash && before.size === after.size && before.component === after.component;
|
|
16610
|
+
});
|
|
16611
|
+
}
|
|
16612
|
+
function isIsoTimestamp(value) {
|
|
16613
|
+
if (typeof value !== "string") {
|
|
16614
|
+
return false;
|
|
16615
|
+
}
|
|
16616
|
+
const time = new Date(value).getTime();
|
|
16617
|
+
return !Number.isNaN(time) && new Date(time).toISOString() === value;
|
|
16618
|
+
}
|
|
16619
|
+
function preserveGeneratedAt(next, previous) {
|
|
16620
|
+
if (previous === null || !isIsoTimestamp(previous.generatedAt)) {
|
|
16621
|
+
return next;
|
|
16622
|
+
}
|
|
16623
|
+
if (!hasSameLockfileContent(next, previous)) {
|
|
16624
|
+
return next;
|
|
16625
|
+
}
|
|
16626
|
+
return { ...next, generatedAt: previous.generatedAt };
|
|
16627
|
+
}
|
|
16628
|
+
async function generateAndWriteLockfileForDir(targetDir, options = {}) {
|
|
16474
16629
|
try {
|
|
16475
16630
|
const packageRoot = getPackageRoot();
|
|
16476
16631
|
const manifest = await readJsonFile(join7(packageRoot, "templates", "manifest.json"));
|
|
16477
16632
|
const { version: generatorVersion } = await readJsonFile(join7(packageRoot, "package.json"));
|
|
16478
|
-
const
|
|
16633
|
+
const generated = await generateLockfile(targetDir, generatorVersion, manifest.version, options);
|
|
16634
|
+
const previous = await readLockfile(targetDir);
|
|
16635
|
+
const lockfile = preserveGeneratedAt(generated, previous);
|
|
16479
16636
|
await writeLockfile(targetDir, lockfile);
|
|
16480
16637
|
return { fileCount: Object.keys(lockfile.files).length };
|
|
16481
16638
|
} catch (err) {
|
|
@@ -20869,12 +21026,50 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
20869
21026
|
import { join as join17 } from "node:path";
|
|
20870
21027
|
|
|
20871
21028
|
// src/cli/serve.ts
|
|
21029
|
+
init_home();
|
|
20872
21030
|
import { spawn } from "node:child_process";
|
|
20873
|
-
import {
|
|
20874
|
-
import {
|
|
20875
|
-
import {
|
|
21031
|
+
import { randomUUID } from "node:crypto";
|
|
21032
|
+
import { constants as constants3, existsSync as existsSync3 } from "node:fs";
|
|
21033
|
+
import {
|
|
21034
|
+
access,
|
|
21035
|
+
link,
|
|
21036
|
+
readdir as readdir3,
|
|
21037
|
+
readFile as readFile3,
|
|
21038
|
+
readlink,
|
|
21039
|
+
rename as rename2,
|
|
21040
|
+
stat as stat3,
|
|
21041
|
+
unlink,
|
|
21042
|
+
writeFile as writeFile3
|
|
21043
|
+
} from "node:fs/promises";
|
|
21044
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join16 } from "node:path";
|
|
21045
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
20876
21046
|
var DEFAULT_PORT = 4321;
|
|
20877
|
-
var
|
|
21047
|
+
var PID_FILE_NAME = ".omcustom-serve.pid";
|
|
21048
|
+
var STARTING_RECORD = /^starting:(\d+):/;
|
|
21049
|
+
var MAX_CLAIM_ATTEMPTS = 3;
|
|
21050
|
+
var CLAIM_MAX_AGE_MS = 60000;
|
|
21051
|
+
var PARTIAL_RECORD_GRACE_MS = 1e4;
|
|
21052
|
+
var ABSENT_RETRY_DELAY_MS = 10;
|
|
21053
|
+
var HARD_LINK_UNSUPPORTED = new Set(["ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV"]);
|
|
21054
|
+
var SIBLING_REMNANT = /^\.omcustom-serve\.pid\.(\d+)\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.(?:tmp|taken)$/;
|
|
21055
|
+
|
|
21056
|
+
class ServePidFileError extends Error {
|
|
21057
|
+
reason;
|
|
21058
|
+
pidFile;
|
|
21059
|
+
constructor(reason, pidFile, options) {
|
|
21060
|
+
super(reason === "home-unresolved" ? "Cannot resolve the serve PID file: the home directory is empty or relative" : `Cannot write the serve PID file: ${pidFile}`, options);
|
|
21061
|
+
this.name = "ServePidFileError";
|
|
21062
|
+
this.reason = reason;
|
|
21063
|
+
this.pidFile = pidFile;
|
|
21064
|
+
}
|
|
21065
|
+
}
|
|
21066
|
+
function resolveServePidFile(sources) {
|
|
21067
|
+
const home = resolveHomeDir(sources);
|
|
21068
|
+
if (!isAbsolute4(home)) {
|
|
21069
|
+
return null;
|
|
21070
|
+
}
|
|
21071
|
+
return join16(home, PID_FILE_NAME);
|
|
21072
|
+
}
|
|
20878
21073
|
function findServeBuildDir(projectRoot, options) {
|
|
20879
21074
|
const localBuild = join16(projectRoot, "packages", "serve", "build");
|
|
20880
21075
|
if (existsSync3(join16(localBuild, "index.js")))
|
|
@@ -20886,29 +21081,94 @@ function findServeBuildDir(projectRoot, options) {
|
|
|
20886
21081
|
}
|
|
20887
21082
|
return null;
|
|
20888
21083
|
}
|
|
20889
|
-
|
|
21084
|
+
function parsePidRecord(raw) {
|
|
21085
|
+
const text = raw.trim();
|
|
21086
|
+
const match = STARTING_RECORD.exec(text);
|
|
21087
|
+
const pid = Number(match === null ? text : match[1]);
|
|
21088
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
21089
|
+
return null;
|
|
21090
|
+
}
|
|
21091
|
+
return { pid, starting: match !== null };
|
|
21092
|
+
}
|
|
21093
|
+
function isProcessAlive(pid) {
|
|
20890
21094
|
try {
|
|
20891
|
-
const raw = await readFile3(PID_FILE, "utf-8");
|
|
20892
|
-
const pid = Number(raw.trim());
|
|
20893
|
-
if (!Number.isFinite(pid) || pid <= 0) {
|
|
20894
|
-
await cleanupPidFile();
|
|
20895
|
-
return false;
|
|
20896
|
-
}
|
|
20897
21095
|
process.kill(pid, 0);
|
|
20898
21096
|
return true;
|
|
20899
21097
|
} catch {
|
|
20900
|
-
await cleanupPidFile();
|
|
20901
21098
|
return false;
|
|
20902
21099
|
}
|
|
20903
21100
|
}
|
|
21101
|
+
function errorCode(error) {
|
|
21102
|
+
return error?.code;
|
|
21103
|
+
}
|
|
21104
|
+
async function isOlderThan(path, ms) {
|
|
21105
|
+
try {
|
|
21106
|
+
const { mtimeMs } = await stat3(path);
|
|
21107
|
+
return Date.now() - mtimeMs >= ms;
|
|
21108
|
+
} catch {
|
|
21109
|
+
return false;
|
|
21110
|
+
}
|
|
21111
|
+
}
|
|
21112
|
+
async function isRecordLive(pidFile, record) {
|
|
21113
|
+
if (!isProcessAlive(record.pid)) {
|
|
21114
|
+
return false;
|
|
21115
|
+
}
|
|
21116
|
+
return !record.starting || !await isOlderThan(pidFile, CLAIM_MAX_AGE_MS);
|
|
21117
|
+
}
|
|
21118
|
+
async function readPidFileSettled(pidFile) {
|
|
21119
|
+
try {
|
|
21120
|
+
return await readFile3(pidFile, "utf-8");
|
|
21121
|
+
} catch (error) {
|
|
21122
|
+
if (errorCode(error) !== "ENOENT") {
|
|
21123
|
+
throw error;
|
|
21124
|
+
}
|
|
21125
|
+
}
|
|
21126
|
+
await delay(ABSENT_RETRY_DELAY_MS);
|
|
21127
|
+
return readFile3(pidFile, "utf-8");
|
|
21128
|
+
}
|
|
21129
|
+
async function isServeRunning() {
|
|
21130
|
+
const pidFile = resolveServePidFile();
|
|
21131
|
+
if (pidFile === null) {
|
|
21132
|
+
return false;
|
|
21133
|
+
}
|
|
21134
|
+
return isServeRunningAt(pidFile);
|
|
21135
|
+
}
|
|
21136
|
+
async function isServeRunningAt(pidFile) {
|
|
21137
|
+
let raw;
|
|
21138
|
+
try {
|
|
21139
|
+
raw = await readPidFileSettled(pidFile);
|
|
21140
|
+
} catch {
|
|
21141
|
+
return false;
|
|
21142
|
+
}
|
|
21143
|
+
const record = parsePidRecord(raw);
|
|
21144
|
+
return record !== null && await isRecordLive(pidFile, record);
|
|
21145
|
+
}
|
|
20904
21146
|
async function startServeBackground(projectRoot, port = DEFAULT_PORT, buildDirOpts) {
|
|
20905
|
-
|
|
21147
|
+
const pidFile = resolveServePidFile();
|
|
21148
|
+
if (pidFile === null) {
|
|
21149
|
+
throw new ServePidFileError("home-unresolved", null);
|
|
21150
|
+
}
|
|
21151
|
+
if (await isServeRunningAt(pidFile)) {
|
|
20906
21152
|
return;
|
|
20907
21153
|
}
|
|
20908
21154
|
const buildDir = findServeBuildDir(projectRoot, buildDirOpts);
|
|
20909
21155
|
if (buildDir === null) {
|
|
20910
21156
|
return;
|
|
20911
21157
|
}
|
|
21158
|
+
try {
|
|
21159
|
+
await access(dirname7(pidFile), constants3.W_OK);
|
|
21160
|
+
} catch (error) {
|
|
21161
|
+
throw new ServePidFileError("pid-not-writable", pidFile, { cause: error });
|
|
21162
|
+
}
|
|
21163
|
+
let claim;
|
|
21164
|
+
try {
|
|
21165
|
+
claim = await claimPidFile(pidFile);
|
|
21166
|
+
} catch (error) {
|
|
21167
|
+
throw new ServePidFileError("pid-not-writable", pidFile, { cause: error });
|
|
21168
|
+
}
|
|
21169
|
+
if (claim === null) {
|
|
21170
|
+
return;
|
|
21171
|
+
}
|
|
20912
21172
|
const child = spawn("node", [join16(buildDir, "index.js")], {
|
|
20913
21173
|
env: {
|
|
20914
21174
|
...process.env,
|
|
@@ -20920,30 +21180,220 @@ async function startServeBackground(projectRoot, port = DEFAULT_PORT, buildDirOp
|
|
|
20920
21180
|
stdio: "ignore",
|
|
20921
21181
|
detached: true
|
|
20922
21182
|
});
|
|
21183
|
+
child.on("error", () => {});
|
|
20923
21184
|
child.unref();
|
|
20924
|
-
if (child.pid
|
|
20925
|
-
await
|
|
21185
|
+
if (child.pid === undefined) {
|
|
21186
|
+
await removePidFileIf(pidFile, claim);
|
|
21187
|
+
return;
|
|
21188
|
+
}
|
|
21189
|
+
let recorded;
|
|
21190
|
+
try {
|
|
21191
|
+
recorded = await recordServerPid(pidFile, claim, String(child.pid));
|
|
21192
|
+
} catch (error) {
|
|
21193
|
+
child.kill("SIGTERM");
|
|
21194
|
+
await removePidFileIf(pidFile, claim);
|
|
21195
|
+
throw new ServePidFileError("pid-not-writable", pidFile, { cause: error });
|
|
21196
|
+
}
|
|
21197
|
+
if (!recorded) {
|
|
21198
|
+
child.kill("SIGTERM");
|
|
20926
21199
|
}
|
|
20927
21200
|
}
|
|
21201
|
+
async function recordServerPid(pidFile, claim, pid) {
|
|
21202
|
+
for (let attempt = 0;attempt < MAX_CLAIM_ATTEMPTS; attempt++) {
|
|
21203
|
+
if (await createPidFileExclusive(pidFile, pid)) {
|
|
21204
|
+
return true;
|
|
21205
|
+
}
|
|
21206
|
+
let current;
|
|
21207
|
+
try {
|
|
21208
|
+
current = await readFile3(pidFile, "utf-8");
|
|
21209
|
+
} catch (error) {
|
|
21210
|
+
if (errorCode(error) === "ENOENT") {
|
|
21211
|
+
continue;
|
|
21212
|
+
}
|
|
21213
|
+
throw error;
|
|
21214
|
+
}
|
|
21215
|
+
if (current !== claim) {
|
|
21216
|
+
return false;
|
|
21217
|
+
}
|
|
21218
|
+
await replacePidFile(pidFile, pid);
|
|
21219
|
+
return true;
|
|
21220
|
+
}
|
|
21221
|
+
return false;
|
|
21222
|
+
}
|
|
20928
21223
|
async function stopServe() {
|
|
21224
|
+
const pidFile = resolveServePidFile();
|
|
21225
|
+
if (pidFile === null) {
|
|
21226
|
+
return false;
|
|
21227
|
+
}
|
|
21228
|
+
let raw;
|
|
20929
21229
|
try {
|
|
20930
|
-
|
|
20931
|
-
|
|
20932
|
-
|
|
20933
|
-
|
|
20934
|
-
|
|
21230
|
+
raw = await readPidFileSettled(pidFile);
|
|
21231
|
+
} catch {
|
|
21232
|
+
return false;
|
|
21233
|
+
}
|
|
21234
|
+
const record = parsePidRecord(raw);
|
|
21235
|
+
if (record === null || record.starting) {
|
|
21236
|
+
return false;
|
|
21237
|
+
}
|
|
21238
|
+
try {
|
|
21239
|
+
process.kill(record.pid, "SIGTERM");
|
|
21240
|
+
} catch {
|
|
21241
|
+
return false;
|
|
21242
|
+
}
|
|
21243
|
+
await removePidFileIf(pidFile, raw);
|
|
21244
|
+
return true;
|
|
21245
|
+
}
|
|
21246
|
+
function siblingPath(pidFile, suffix) {
|
|
21247
|
+
return `${pidFile}.${process.pid}.${randomUUID()}.${suffix}`;
|
|
21248
|
+
}
|
|
21249
|
+
async function claimPidFile(pidFile) {
|
|
21250
|
+
await removeDeadSiblings(pidFile);
|
|
21251
|
+
const claim = `starting:${process.pid}:${randomUUID()}`;
|
|
21252
|
+
for (let attempt = 0;attempt < MAX_CLAIM_ATTEMPTS; attempt++) {
|
|
21253
|
+
if (await createPidFileExclusive(pidFile, claim)) {
|
|
21254
|
+
return claim;
|
|
21255
|
+
}
|
|
21256
|
+
let raw;
|
|
21257
|
+
try {
|
|
21258
|
+
raw = await readFile3(pidFile, "utf-8");
|
|
21259
|
+
} catch (error) {
|
|
21260
|
+
if (errorCode(error) === "ENOENT") {
|
|
21261
|
+
await removeDanglingLink(pidFile);
|
|
21262
|
+
continue;
|
|
21263
|
+
}
|
|
21264
|
+
throw error;
|
|
21265
|
+
}
|
|
21266
|
+
if (await isRecordHeld(pidFile, raw)) {
|
|
21267
|
+
return null;
|
|
20935
21268
|
}
|
|
20936
|
-
|
|
20937
|
-
|
|
21269
|
+
await removePidFileIf(pidFile, raw);
|
|
21270
|
+
}
|
|
21271
|
+
return null;
|
|
21272
|
+
}
|
|
21273
|
+
async function isRecordHeld(pidFile, raw) {
|
|
21274
|
+
const record = parsePidRecord(raw);
|
|
21275
|
+
if (record === null) {
|
|
21276
|
+
return isPossiblyPartialRecord(raw) && !await isOlderThan(pidFile, PARTIAL_RECORD_GRACE_MS);
|
|
21277
|
+
}
|
|
21278
|
+
return isRecordLive(pidFile, record);
|
|
21279
|
+
}
|
|
21280
|
+
function isPossiblyPartialRecord(raw) {
|
|
21281
|
+
return "starting:".startsWith(raw) || /^starting:\d*$/.test(raw);
|
|
21282
|
+
}
|
|
21283
|
+
async function removeDeadSiblings(pidFile) {
|
|
21284
|
+
const dir = dirname7(pidFile);
|
|
21285
|
+
let names;
|
|
21286
|
+
try {
|
|
21287
|
+
names = await readdir3(dir);
|
|
21288
|
+
} catch {
|
|
21289
|
+
return;
|
|
21290
|
+
}
|
|
21291
|
+
for (const name of names) {
|
|
21292
|
+
const match = SIBLING_REMNANT.exec(name);
|
|
21293
|
+
if (match !== null && isProcessGone(Number(match[1]))) {
|
|
21294
|
+
await unlinkQuietly(join16(dir, name));
|
|
21295
|
+
}
|
|
21296
|
+
}
|
|
21297
|
+
}
|
|
21298
|
+
function isProcessGone(pid) {
|
|
21299
|
+
try {
|
|
21300
|
+
process.kill(pid, 0);
|
|
21301
|
+
return false;
|
|
21302
|
+
} catch (error) {
|
|
21303
|
+
return errorCode(error) === "ESRCH";
|
|
21304
|
+
}
|
|
21305
|
+
}
|
|
21306
|
+
async function createPidFileExclusive(pidFile, content) {
|
|
21307
|
+
const staged = siblingPath(pidFile, "tmp");
|
|
21308
|
+
try {
|
|
21309
|
+
await writeFile3(staged, content, { encoding: "utf-8", flag: "wx" });
|
|
21310
|
+
await linkExclusive(staged, pidFile, content);
|
|
20938
21311
|
return true;
|
|
21312
|
+
} catch (error) {
|
|
21313
|
+
if (errorCode(error) === "EEXIST") {
|
|
21314
|
+
return false;
|
|
21315
|
+
}
|
|
21316
|
+
throw error;
|
|
21317
|
+
} finally {
|
|
21318
|
+
await unlinkQuietly(staged);
|
|
21319
|
+
}
|
|
21320
|
+
}
|
|
21321
|
+
async function linkExclusive(source, target, content) {
|
|
21322
|
+
try {
|
|
21323
|
+
await link(source, target);
|
|
21324
|
+
} catch (error) {
|
|
21325
|
+
if (!HARD_LINK_UNSUPPORTED.has(errorCode(error) ?? "")) {
|
|
21326
|
+
throw error;
|
|
21327
|
+
}
|
|
21328
|
+
await writeFile3(target, content, { encoding: "utf-8", flag: "wx" });
|
|
21329
|
+
}
|
|
21330
|
+
}
|
|
21331
|
+
async function replacePidFile(pidFile, content) {
|
|
21332
|
+
const staged = siblingPath(pidFile, "tmp");
|
|
21333
|
+
try {
|
|
21334
|
+
await writeFile3(staged, content, { encoding: "utf-8", flag: "wx" });
|
|
21335
|
+
await rename2(staged, pidFile);
|
|
21336
|
+
} catch (error) {
|
|
21337
|
+
await unlinkQuietly(staged);
|
|
21338
|
+
throw error;
|
|
21339
|
+
}
|
|
21340
|
+
}
|
|
21341
|
+
async function removePidFileIf(pidFile, expected) {
|
|
21342
|
+
await removePidFileWhen(pidFile, async (taken) => {
|
|
21343
|
+
try {
|
|
21344
|
+
return await readFile3(taken, "utf-8") === expected;
|
|
21345
|
+
} catch {
|
|
21346
|
+
return false;
|
|
21347
|
+
}
|
|
21348
|
+
});
|
|
21349
|
+
}
|
|
21350
|
+
async function removeDanglingLink(pidFile) {
|
|
21351
|
+
if (!await isDanglingLink(pidFile)) {
|
|
21352
|
+
return;
|
|
21353
|
+
}
|
|
21354
|
+
await removePidFileWhen(pidFile, isDanglingLink);
|
|
21355
|
+
}
|
|
21356
|
+
async function isDanglingLink(path) {
|
|
21357
|
+
try {
|
|
21358
|
+
await readlink(path);
|
|
20939
21359
|
} catch {
|
|
20940
|
-
await cleanupPidFile();
|
|
20941
21360
|
return false;
|
|
20942
21361
|
}
|
|
21362
|
+
try {
|
|
21363
|
+
await stat3(path);
|
|
21364
|
+
return false;
|
|
21365
|
+
} catch {
|
|
21366
|
+
return true;
|
|
21367
|
+
}
|
|
21368
|
+
}
|
|
21369
|
+
async function removePidFileWhen(pidFile, isExpected) {
|
|
21370
|
+
const taken = siblingPath(pidFile, "taken");
|
|
21371
|
+
try {
|
|
21372
|
+
await rename2(pidFile, taken);
|
|
21373
|
+
} catch {
|
|
21374
|
+
return;
|
|
21375
|
+
}
|
|
21376
|
+
if (!await isExpected(taken)) {
|
|
21377
|
+
await putBack(taken, pidFile);
|
|
21378
|
+
}
|
|
21379
|
+
await unlinkQuietly(taken);
|
|
21380
|
+
}
|
|
21381
|
+
async function putBack(taken, pidFile) {
|
|
21382
|
+
try {
|
|
21383
|
+
await link(taken, pidFile);
|
|
21384
|
+
return;
|
|
21385
|
+
} catch (error) {
|
|
21386
|
+
if (!HARD_LINK_UNSUPPORTED.has(errorCode(error) ?? "")) {
|
|
21387
|
+
return;
|
|
21388
|
+
}
|
|
21389
|
+
}
|
|
21390
|
+
try {
|
|
21391
|
+
await writeFile3(pidFile, await readFile3(taken, "utf-8"), { encoding: "utf-8", flag: "wx" });
|
|
21392
|
+
} catch {}
|
|
20943
21393
|
}
|
|
20944
|
-
async function
|
|
21394
|
+
async function unlinkQuietly(path) {
|
|
20945
21395
|
try {
|
|
20946
|
-
await unlink(
|
|
21396
|
+
await unlink(path);
|
|
20947
21397
|
} catch {}
|
|
20948
21398
|
}
|
|
20949
21399
|
|
|
@@ -20962,7 +21412,15 @@ async function serveCommand(options) {
|
|
|
20962
21412
|
runForeground(cwd, port, buildDirOpts);
|
|
20963
21413
|
return;
|
|
20964
21414
|
}
|
|
20965
|
-
|
|
21415
|
+
try {
|
|
21416
|
+
await startServeBackground(cwd, port, buildDirOpts);
|
|
21417
|
+
} catch (error) {
|
|
21418
|
+
if (!(error instanceof ServePidFileError)) {
|
|
21419
|
+
throw error;
|
|
21420
|
+
}
|
|
21421
|
+
console.error(describePidFileError(error));
|
|
21422
|
+
process.exit(1);
|
|
21423
|
+
}
|
|
20966
21424
|
const running = await isServeRunning();
|
|
20967
21425
|
if (running) {
|
|
20968
21426
|
console.log(i18n.t("cli.web.start.started", { port }));
|
|
@@ -20971,6 +21429,13 @@ async function serveCommand(options) {
|
|
|
20971
21429
|
process.exit(1);
|
|
20972
21430
|
}
|
|
20973
21431
|
}
|
|
21432
|
+
function describePidFileError(error) {
|
|
21433
|
+
if (error.reason === "home-unresolved") {
|
|
21434
|
+
return i18n.t("cli.web.start.homeUnresolved");
|
|
21435
|
+
}
|
|
21436
|
+
const cause = error.cause instanceof Error ? error.cause.message : String(error.cause);
|
|
21437
|
+
return i18n.t("cli.web.start.pidNotWritable", { path: String(error.pidFile), error: cause });
|
|
21438
|
+
}
|
|
20974
21439
|
async function serveStopCommand() {
|
|
20975
21440
|
const stopped = await stopServe();
|
|
20976
21441
|
if (stopped) {
|