mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-18 23:03:06 +08:00
fix(installer): harden locale docs install
This commit is contained in:
@@ -531,31 +531,31 @@
|
|||||||
"family": "locale",
|
"family": "locale",
|
||||||
"description": "Japanese (ja-JP) translated reference docs installed to ~/.claude/docs/ja-JP/.",
|
"description": "Japanese (ja-JP) translated reference docs installed to ~/.claude/docs/ja-JP/.",
|
||||||
"modules": [
|
"modules": [
|
||||||
"docs-ja-JP"
|
"docs-ja-jp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "locale:zh-CN",
|
"id": "locale:zh-cn",
|
||||||
"family": "locale",
|
"family": "locale",
|
||||||
"description": "Simplified Chinese (zh-CN) translated reference docs installed to ~/.claude/docs/zh-CN/.",
|
"description": "Simplified Chinese (zh-CN) translated reference docs installed to ~/.claude/docs/zh-CN/.",
|
||||||
"modules": [
|
"modules": [
|
||||||
"docs-zh-CN"
|
"docs-zh-cn"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "locale:ko-KR",
|
"id": "locale:ko-kr",
|
||||||
"family": "locale",
|
"family": "locale",
|
||||||
"description": "Korean (ko-KR) translated reference docs installed to ~/.claude/docs/ko-KR/.",
|
"description": "Korean (ko-KR) translated reference docs installed to ~/.claude/docs/ko-KR/.",
|
||||||
"modules": [
|
"modules": [
|
||||||
"docs-ko-KR"
|
"docs-ko-kr"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "locale:pt-BR",
|
"id": "locale:pt-br",
|
||||||
"family": "locale",
|
"family": "locale",
|
||||||
"description": "Brazilian Portuguese (pt-BR) translated reference docs installed to ~/.claude/docs/pt-BR/.",
|
"description": "Brazilian Portuguese (pt-BR) translated reference docs installed to ~/.claude/docs/pt-BR/.",
|
||||||
"modules": [
|
"modules": [
|
||||||
"docs-pt-BR"
|
"docs-pt-br"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -575,19 +575,19 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "locale:vi-VN",
|
"id": "locale:vi-vn",
|
||||||
"family": "locale",
|
"family": "locale",
|
||||||
"description": "Vietnamese (vi-VN) translated reference docs installed to ~/.claude/docs/vi-VN/.",
|
"description": "Vietnamese (vi-VN) translated reference docs installed to ~/.claude/docs/vi-VN/.",
|
||||||
"modules": [
|
"modules": [
|
||||||
"docs-vi-VN"
|
"docs-vi-vn"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "locale:zh-TW",
|
"id": "locale:zh-tw",
|
||||||
"family": "locale",
|
"family": "locale",
|
||||||
"description": "Traditional Chinese (zh-TW) translated reference docs installed to ~/.claude/docs/zh-TW/.",
|
"description": "Traditional Chinese (zh-TW) translated reference docs installed to ~/.claude/docs/zh-TW/.",
|
||||||
"modules": [
|
"modules": [
|
||||||
"docs-zh-TW"
|
"docs-zh-tw"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -695,7 +695,7 @@
|
|||||||
"stability": "stable"
|
"stability": "stable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "docs-ja-JP",
|
"id": "docs-ja-jp",
|
||||||
"kind": "docs",
|
"kind": "docs",
|
||||||
"description": "Japanese (ja-JP) translated reference docs for agents, commands, skills, and rules.",
|
"description": "Japanese (ja-JP) translated reference docs for agents, commands, skills, and rules.",
|
||||||
"paths": [
|
"paths": [
|
||||||
@@ -710,7 +710,7 @@
|
|||||||
"stability": "stable"
|
"stability": "stable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "docs-zh-CN",
|
"id": "docs-zh-cn",
|
||||||
"kind": "docs",
|
"kind": "docs",
|
||||||
"description": "Simplified Chinese (zh-CN) translated reference docs for agents, commands, skills, and rules.",
|
"description": "Simplified Chinese (zh-CN) translated reference docs for agents, commands, skills, and rules.",
|
||||||
"paths": [
|
"paths": [
|
||||||
@@ -725,7 +725,7 @@
|
|||||||
"stability": "stable"
|
"stability": "stable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "docs-ko-KR",
|
"id": "docs-ko-kr",
|
||||||
"kind": "docs",
|
"kind": "docs",
|
||||||
"description": "Korean (ko-KR) translated reference docs for agents, commands, skills, and rules.",
|
"description": "Korean (ko-KR) translated reference docs for agents, commands, skills, and rules.",
|
||||||
"paths": [
|
"paths": [
|
||||||
@@ -740,7 +740,7 @@
|
|||||||
"stability": "stable"
|
"stability": "stable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "docs-pt-BR",
|
"id": "docs-pt-br",
|
||||||
"kind": "docs",
|
"kind": "docs",
|
||||||
"description": "Brazilian Portuguese (pt-BR) translated reference docs for agents, commands, skills, and rules.",
|
"description": "Brazilian Portuguese (pt-BR) translated reference docs for agents, commands, skills, and rules.",
|
||||||
"paths": [
|
"paths": [
|
||||||
@@ -785,7 +785,7 @@
|
|||||||
"stability": "stable"
|
"stability": "stable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "docs-vi-VN",
|
"id": "docs-vi-vn",
|
||||||
"kind": "docs",
|
"kind": "docs",
|
||||||
"description": "Vietnamese (vi-VN) translated reference docs for agents, commands, skills, and rules.",
|
"description": "Vietnamese (vi-VN) translated reference docs for agents, commands, skills, and rules.",
|
||||||
"paths": [
|
"paths": [
|
||||||
@@ -800,7 +800,7 @@
|
|||||||
"stability": "stable"
|
"stability": "stable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "docs-zh-TW",
|
"id": "docs-zh-tw",
|
||||||
"kind": "docs",
|
"kind": "docs",
|
||||||
"description": "Traditional Chinese (zh-TW) translated reference docs for agents, commands, skills, and rules.",
|
"description": "Traditional Chinese (zh-TW) translated reference docs for agents, commands, skills, and rules.",
|
||||||
"paths": [
|
"paths": [
|
||||||
|
|||||||
@@ -56,6 +56,14 @@
|
|||||||
"agent.yaml",
|
"agent.yaml",
|
||||||
"agents/",
|
"agents/",
|
||||||
"commands/",
|
"commands/",
|
||||||
|
"docs/ja-JP/",
|
||||||
|
"docs/ko-KR/",
|
||||||
|
"docs/pt-BR/",
|
||||||
|
"docs/ru/",
|
||||||
|
"docs/tr/",
|
||||||
|
"docs/vi-VN/",
|
||||||
|
"docs/zh-CN/",
|
||||||
|
"docs/zh-TW/",
|
||||||
"hooks/",
|
"hooks/",
|
||||||
"install.ps1",
|
"install.ps1",
|
||||||
"install.sh",
|
"install.sh",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^(baseline|lang|framework|capability|agent|skill):[a-z0-9-]+$"
|
"pattern": "^(baseline|lang|framework|capability|agent|skill|locale):[a-z0-9-]+$"
|
||||||
},
|
},
|
||||||
"family": {
|
"family": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
"framework",
|
"framework",
|
||||||
"capability",
|
"capability",
|
||||||
"agent",
|
"agent",
|
||||||
"skill"
|
"skill",
|
||||||
|
"locale"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
"hooks",
|
"hooks",
|
||||||
"platform",
|
"platform",
|
||||||
"orchestration",
|
"orchestration",
|
||||||
"skills"
|
"skills",
|
||||||
|
"docs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const COMPONENT_FAMILY_PREFIXES = {
|
|||||||
language: 'lang:',
|
language: 'lang:',
|
||||||
framework: 'framework:',
|
framework: 'framework:',
|
||||||
capability: 'capability:',
|
capability: 'capability:',
|
||||||
|
locale: 'locale:',
|
||||||
};
|
};
|
||||||
|
|
||||||
function readJson(filePath, label) {
|
function readJson(filePath, label) {
|
||||||
@@ -163,9 +164,12 @@ function validateInstallManifests() {
|
|||||||
|
|
||||||
if (profiles.full) {
|
if (profiles.full) {
|
||||||
const fullModules = new Set(profiles.full.modules);
|
const fullModules = new Set(profiles.full.modules);
|
||||||
for (const moduleId of moduleIds) {
|
for (const module of modules) {
|
||||||
if (!fullModules.has(moduleId)) {
|
if (module.kind === 'docs' && module.defaultInstall === false) {
|
||||||
console.error(`ERROR: full profile is missing module ${moduleId}`);
|
continue;
|
||||||
|
}
|
||||||
|
if (!fullModules.has(module.id)) {
|
||||||
|
console.error(`ERROR: full profile is missing module ${module.id}`);
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,6 +555,12 @@ function createLegacyCompatInstallPlan(options = {}) {
|
|||||||
const sourceRoot = options.sourceRoot || getSourceRoot();
|
const sourceRoot = options.sourceRoot || getSourceRoot();
|
||||||
const projectRoot = options.projectRoot || process.cwd();
|
const projectRoot = options.projectRoot || process.cwd();
|
||||||
const target = options.target || 'claude';
|
const target = options.target || 'claude';
|
||||||
|
const includeComponentIds = Array.isArray(options.includeComponentIds)
|
||||||
|
? [...options.includeComponentIds]
|
||||||
|
: [];
|
||||||
|
const excludeComponentIds = Array.isArray(options.excludeComponentIds)
|
||||||
|
? [...options.excludeComponentIds]
|
||||||
|
: [];
|
||||||
|
|
||||||
validateLegacyTarget(target);
|
validateLegacyTarget(target);
|
||||||
|
|
||||||
@@ -571,14 +577,14 @@ function createLegacyCompatInstallPlan(options = {}) {
|
|||||||
target,
|
target,
|
||||||
profileId: null,
|
profileId: null,
|
||||||
moduleIds: selection.moduleIds,
|
moduleIds: selection.moduleIds,
|
||||||
includeComponentIds: [],
|
includeComponentIds,
|
||||||
excludeComponentIds: [],
|
excludeComponentIds,
|
||||||
legacyLanguages: selection.legacyLanguages,
|
legacyLanguages: selection.legacyLanguages,
|
||||||
legacyMode: true,
|
legacyMode: true,
|
||||||
requestProfileId: null,
|
requestProfileId: null,
|
||||||
requestModuleIds: [],
|
requestModuleIds: [],
|
||||||
requestIncludeComponentIds: [],
|
requestIncludeComponentIds: includeComponentIds,
|
||||||
requestExcludeComponentIds: [],
|
requestExcludeComponentIds: excludeComponentIds,
|
||||||
mode: 'legacy-compat',
|
mode: 'legacy-compat',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ const SUPPORTED_LOCALES = Object.freeze(['ja', 'zh-CN', 'ko-KR', 'pt-BR', 'ru',
|
|||||||
const LOCALE_ALIAS_TO_COMPONENT_ID = Object.freeze({
|
const LOCALE_ALIAS_TO_COMPONENT_ID = Object.freeze({
|
||||||
'ja': 'locale:ja',
|
'ja': 'locale:ja',
|
||||||
'ja-JP': 'locale:ja',
|
'ja-JP': 'locale:ja',
|
||||||
'zh-CN': 'locale:zh-CN',
|
'zh-CN': 'locale:zh-cn',
|
||||||
'zh': 'locale:zh-CN',
|
'zh': 'locale:zh-cn',
|
||||||
'ko-KR': 'locale:ko-KR',
|
'ko-KR': 'locale:ko-kr',
|
||||||
'ko': 'locale:ko-KR',
|
'ko': 'locale:ko-kr',
|
||||||
'pt-BR': 'locale:pt-BR',
|
'pt-BR': 'locale:pt-br',
|
||||||
'pt': 'locale:pt-BR',
|
'pt': 'locale:pt-br',
|
||||||
'ru': 'locale:ru',
|
'ru': 'locale:ru',
|
||||||
'tr': 'locale:tr',
|
'tr': 'locale:tr',
|
||||||
'vi-VN': 'locale:vi-VN',
|
'vi-VN': 'locale:vi-vn',
|
||||||
'vi': 'locale:vi-VN',
|
'vi': 'locale:vi-vn',
|
||||||
'zh-TW': 'locale:zh-TW',
|
'zh-TW': 'locale:zh-tw',
|
||||||
});
|
});
|
||||||
|
|
||||||
function listSupportedLocales() {
|
function listSupportedLocales() {
|
||||||
|
|||||||
@@ -62,7 +62,11 @@ function parseInstallArgs(argv) {
|
|||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
} else if (arg === '--locale') {
|
} else if (arg === '--locale') {
|
||||||
parsed.locale = args[index + 1] || null;
|
const locale = args[index + 1] || '';
|
||||||
|
if (!locale || locale.startsWith('--')) {
|
||||||
|
throw new Error('Missing value for --locale');
|
||||||
|
}
|
||||||
|
parsed.locale = locale;
|
||||||
index += 1;
|
index += 1;
|
||||||
} else if (arg === '--dry-run') {
|
} else if (arg === '--dry-run') {
|
||||||
parsed.dryRun = true;
|
parsed.dryRun = true;
|
||||||
@@ -85,6 +89,7 @@ function normalizeInstallRequest(options = {}) {
|
|||||||
? options.config
|
? options.config
|
||||||
: null;
|
: null;
|
||||||
const profileId = options.profileId || config?.profileId || null;
|
const profileId = options.profileId || config?.profileId || null;
|
||||||
|
const target = options.target || config?.target || 'claude';
|
||||||
const moduleIds = validateInstallModuleIds(
|
const moduleIds = validateInstallModuleIds(
|
||||||
dedupeStrings([...(config?.moduleIds || []), ...(options.moduleIds || [])])
|
dedupeStrings([...(config?.moduleIds || []), ...(options.moduleIds || [])])
|
||||||
);
|
);
|
||||||
@@ -95,9 +100,15 @@ function normalizeInstallRequest(options = {}) {
|
|||||||
`Unsupported locale: "${locale}". Supported locales: ${listSupportedLocales().join(', ')}`
|
`Unsupported locale: "${locale}". Supported locales: ${listSupportedLocales().join(', ')}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const includeComponentIds = dedupeStrings([
|
if (locale && target !== 'claude') {
|
||||||
|
throw new Error('--locale can only be used with --target claude');
|
||||||
|
}
|
||||||
|
const requestedIncludeComponentIds = dedupeStrings([
|
||||||
...(config?.includeComponentIds || []),
|
...(config?.includeComponentIds || []),
|
||||||
...(options.includeComponentIds || []),
|
...(options.includeComponentIds || []),
|
||||||
|
]);
|
||||||
|
const includeComponentIds = dedupeStrings([
|
||||||
|
...requestedIncludeComponentIds,
|
||||||
...(localeComponentId ? [localeComponentId] : []),
|
...(localeComponentId ? [localeComponentId] : []),
|
||||||
]);
|
]);
|
||||||
const excludeComponentIds = dedupeStrings([
|
const excludeComponentIds = dedupeStrings([
|
||||||
@@ -108,13 +119,16 @@ function normalizeInstallRequest(options = {}) {
|
|||||||
...(Array.isArray(options.legacyLanguages) ? options.legacyLanguages : []),
|
...(Array.isArray(options.legacyLanguages) ? options.legacyLanguages : []),
|
||||||
...(Array.isArray(options.languages) ? options.languages : []),
|
...(Array.isArray(options.languages) ? options.languages : []),
|
||||||
]).map(language => language.toLowerCase()));
|
]).map(language => language.toLowerCase()));
|
||||||
const target = options.target || config?.target || 'claude';
|
|
||||||
const hasManifestBaseSelection = Boolean(profileId) || moduleIds.length > 0 || includeComponentIds.length > 0;
|
const hasManifestBaseSelection = Boolean(profileId) || moduleIds.length > 0 || includeComponentIds.length > 0;
|
||||||
|
const hasNonLocaleManifestSelection = Boolean(profileId)
|
||||||
|
|| moduleIds.length > 0
|
||||||
|
|| requestedIncludeComponentIds.length > 0
|
||||||
|
|| excludeComponentIds.length > 0;
|
||||||
const usingManifestMode = hasManifestBaseSelection || excludeComponentIds.length > 0;
|
const usingManifestMode = hasManifestBaseSelection || excludeComponentIds.length > 0;
|
||||||
|
|
||||||
if (usingManifestMode && legacyLanguages.length > 0) {
|
if (hasNonLocaleManifestSelection && legacyLanguages.length > 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Legacy language arguments cannot be combined with --profile, --modules, --with, --without, --locale, or manifest config selections'
|
'Legacy language arguments cannot be combined with --profile, --modules, --with, --without, or manifest config selections'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +137,9 @@ function normalizeInstallRequest(options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mode: usingManifestMode ? 'manifest' : 'legacy-compat',
|
mode: legacyLanguages.length > 0
|
||||||
|
? 'legacy-compat'
|
||||||
|
: (usingManifestMode ? 'manifest' : 'legacy-compat'),
|
||||||
target,
|
target,
|
||||||
profileId,
|
profileId,
|
||||||
moduleIds,
|
moduleIds,
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ function createInstallPlanFromRequest(request, options = {}) {
|
|||||||
return createLegacyCompatInstallPlan({
|
return createLegacyCompatInstallPlan({
|
||||||
target: request.target,
|
target: request.target,
|
||||||
legacyLanguages: request.legacyLanguages,
|
legacyLanguages: request.legacyLanguages,
|
||||||
|
includeComponentIds: request.includeComponentIds,
|
||||||
|
excludeComponentIds: request.excludeComponentIds,
|
||||||
projectRoot: options.projectRoot,
|
projectRoot: options.projectRoot,
|
||||||
homeDir: options.homeDir,
|
homeDir: options.homeDir,
|
||||||
claudeRulesDir: options.claudeRulesDir,
|
claudeRulesDir: options.claudeRulesDir,
|
||||||
|
|||||||
@@ -52,6 +52,29 @@ function runTests() {
|
|||||||
assert.deepStrictEqual(parsed.languages, []);
|
assert.deepStrictEqual(parsed.languages, []);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('parses --locale argument', () => {
|
||||||
|
const parsed = parseInstallArgs([
|
||||||
|
'node',
|
||||||
|
'scripts/install-apply.js',
|
||||||
|
'--locale', 'ja'
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.strictEqual(parsed.locale, 'ja');
|
||||||
|
assert.deepStrictEqual(parsed.languages, []);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('requires a --locale value', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => parseInstallArgs([
|
||||||
|
'node',
|
||||||
|
'scripts/install-apply.js',
|
||||||
|
'--locale',
|
||||||
|
'--dry-run'
|
||||||
|
]),
|
||||||
|
/Missing value for --locale/
|
||||||
|
);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('normalizes legacy language installs into a canonical request', () => {
|
if (test('normalizes legacy language installs into a canonical request', () => {
|
||||||
const request = normalizeInstallRequest({
|
const request = normalizeInstallRequest({
|
||||||
target: 'claude',
|
target: 'claude',
|
||||||
@@ -67,6 +90,69 @@ function runTests() {
|
|||||||
assert.strictEqual(request.profileId, null);
|
assert.strictEqual(request.profileId, null);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('normalizes locale-only installs as manifest component requests', () => {
|
||||||
|
const request = normalizeInstallRequest({
|
||||||
|
target: 'claude',
|
||||||
|
profileId: null,
|
||||||
|
moduleIds: [],
|
||||||
|
includeComponentIds: [],
|
||||||
|
excludeComponentIds: [],
|
||||||
|
languages: [],
|
||||||
|
locale: 'ja',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(request.mode, 'manifest');
|
||||||
|
assert.strictEqual(request.target, 'claude');
|
||||||
|
assert.deepStrictEqual(request.includeComponentIds, ['locale:ja']);
|
||||||
|
assert.deepStrictEqual(request.legacyLanguages, []);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('allows legacy language installs to include a locale component', () => {
|
||||||
|
const request = normalizeInstallRequest({
|
||||||
|
target: 'claude',
|
||||||
|
profileId: null,
|
||||||
|
moduleIds: [],
|
||||||
|
includeComponentIds: [],
|
||||||
|
excludeComponentIds: [],
|
||||||
|
languages: ['typescript'],
|
||||||
|
locale: 'ja-JP',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(request.mode, 'legacy-compat');
|
||||||
|
assert.deepStrictEqual(request.legacyLanguages, ['typescript']);
|
||||||
|
assert.deepStrictEqual(request.includeComponentIds, ['locale:ja']);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('rejects unsupported locale codes', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => normalizeInstallRequest({
|
||||||
|
target: 'claude',
|
||||||
|
profileId: null,
|
||||||
|
moduleIds: [],
|
||||||
|
includeComponentIds: [],
|
||||||
|
excludeComponentIds: [],
|
||||||
|
languages: [],
|
||||||
|
locale: 'fr',
|
||||||
|
}),
|
||||||
|
/Unsupported locale/
|
||||||
|
);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('rejects --locale for non-Claude targets', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => normalizeInstallRequest({
|
||||||
|
target: 'cursor',
|
||||||
|
profileId: null,
|
||||||
|
moduleIds: [],
|
||||||
|
includeComponentIds: [],
|
||||||
|
excludeComponentIds: [],
|
||||||
|
languages: [],
|
||||||
|
locale: 'ja',
|
||||||
|
}),
|
||||||
|
/--locale can only be used with --target claude/
|
||||||
|
);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('normalizes manifest installs into a canonical request', () => {
|
if (test('normalizes manifest installs into a canonical request', () => {
|
||||||
const request = normalizeInstallRequest({
|
const request = normalizeInstallRequest({
|
||||||
target: 'cursor',
|
target: 'cursor',
|
||||||
|
|||||||
172
tests/lib/locale-install.test.js
Normal file
172
tests/lib/locale-install.test.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* Tests for --locale translated docs installs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const { execFileSync } = require('child_process');
|
||||||
|
|
||||||
|
const {
|
||||||
|
listInstallComponents,
|
||||||
|
resolveInstallPlan,
|
||||||
|
} = require('../../scripts/lib/install-manifests');
|
||||||
|
|
||||||
|
function normalizePlanPath(value) {
|
||||||
|
return String(value || '').replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function runInstallApply(args, options = {}) {
|
||||||
|
const scriptPath = path.join(__dirname, '..', '..', 'scripts', 'install-apply.js');
|
||||||
|
return execFileSync('node', [scriptPath, ...args], {
|
||||||
|
cwd: options.cwd || process.cwd(),
|
||||||
|
env: { ...process.env, ...(options.env || {}) },
|
||||||
|
encoding: 'utf8',
|
||||||
|
maxBuffer: 16 * 1024 * 1024,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(name, fn) {
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
console.log(` \u2713 ${name}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` \u2717 ${name}`);
|
||||||
|
console.log(` Error: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
console.log('\n=== Testing --locale translated docs installs ===\n');
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
if (test('component catalog includes locale entries', () => {
|
||||||
|
const components = listInstallComponents({ family: 'locale' });
|
||||||
|
assert.ok(components.some(component => component.id === 'locale:ja'));
|
||||||
|
assert.ok(components.some(component => component.id === 'locale:zh-cn'));
|
||||||
|
assert.ok(components.every(component => component.family === 'locale'));
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('locale component resolves to the translated docs module', () => {
|
||||||
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-plan-'));
|
||||||
|
try {
|
||||||
|
const plan = resolveInstallPlan({
|
||||||
|
includeComponentIds: ['locale:ja'],
|
||||||
|
target: 'claude',
|
||||||
|
homeDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(plan.selectedModuleIds, ['docs-ja-jp']);
|
||||||
|
assert.ok(
|
||||||
|
plan.operations.some(operation => (
|
||||||
|
normalizePlanPath(operation.sourceRelativePath) === 'docs/ja-JP'
|
||||||
|
&& normalizePlanPath(operation.destinationPath).endsWith('/.claude/docs/ja-JP')
|
||||||
|
)),
|
||||||
|
'Should map docs/ja-JP to ~/.claude/docs/ja-JP'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('end-to-end: --locale ja dry-run includes docs-ja-jp operations', () => {
|
||||||
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-dry-run-'));
|
||||||
|
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-dry-run-project-'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const output = runInstallApply([
|
||||||
|
'--locale', 'ja',
|
||||||
|
'--dry-run',
|
||||||
|
'--json',
|
||||||
|
], {
|
||||||
|
cwd: projectDir,
|
||||||
|
env: { HOME: homeDir },
|
||||||
|
});
|
||||||
|
const json = JSON.parse(output);
|
||||||
|
|
||||||
|
assert.strictEqual(json.plan.mode, 'manifest');
|
||||||
|
assert.deepStrictEqual(json.plan.includedComponentIds, ['locale:ja']);
|
||||||
|
assert.deepStrictEqual(json.plan.selectedModuleIds, ['docs-ja-jp']);
|
||||||
|
assert.ok(
|
||||||
|
json.plan.operations.some(operation => (
|
||||||
|
normalizePlanPath(operation.sourceRelativePath) === 'docs/ja-JP/README.md'
|
||||||
|
&& normalizePlanPath(operation.destinationPath).endsWith('/.claude/docs/ja-JP/README.md')
|
||||||
|
)),
|
||||||
|
'Should copy translated README into ~/.claude/docs/ja-JP'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||||
|
fs.rmSync(projectDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('end-to-end: legacy language plus --locale keeps legacy install and docs', () => {
|
||||||
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-legacy-dry-run-'));
|
||||||
|
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-legacy-dry-run-project-'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const output = runInstallApply([
|
||||||
|
'typescript',
|
||||||
|
'--locale', 'ja',
|
||||||
|
'--dry-run',
|
||||||
|
'--json',
|
||||||
|
], {
|
||||||
|
cwd: projectDir,
|
||||||
|
env: { HOME: homeDir },
|
||||||
|
});
|
||||||
|
const json = JSON.parse(output);
|
||||||
|
|
||||||
|
assert.strictEqual(json.plan.mode, 'legacy-compat');
|
||||||
|
assert.deepStrictEqual(json.plan.legacyLanguages, ['typescript']);
|
||||||
|
assert.ok(json.plan.includedComponentIds.includes('locale:ja'));
|
||||||
|
assert.ok(json.plan.selectedModuleIds.includes('framework-language'));
|
||||||
|
assert.ok(json.plan.selectedModuleIds.includes('docs-ja-jp'));
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||||
|
fs.rmSync(projectDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('end-to-end: --locale ja installs translated docs side-by-side', () => {
|
||||||
|
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-install-'));
|
||||||
|
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-install-project-'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
runInstallApply([
|
||||||
|
'--locale', 'ja',
|
||||||
|
], {
|
||||||
|
cwd: projectDir,
|
||||||
|
env: { HOME: homeDir },
|
||||||
|
});
|
||||||
|
|
||||||
|
const claudeRoot = path.join(homeDir, '.claude');
|
||||||
|
assert.ok(
|
||||||
|
fs.existsSync(path.join(claudeRoot, 'docs', 'ja-JP', 'README.md')),
|
||||||
|
'Should install Japanese README under docs/ja-JP'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
!fs.existsSync(path.join(claudeRoot, 'skills', 'ecc', 'configure-ecc', 'SKILL.md')),
|
||||||
|
'Locale-only install should not install English skills'
|
||||||
|
);
|
||||||
|
|
||||||
|
const statePath = path.join(claudeRoot, 'ecc', 'install-state.json');
|
||||||
|
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
||||||
|
assert.deepStrictEqual(state.request.includeComponents, ['locale:ja']);
|
||||||
|
assert.deepStrictEqual(state.resolution.selectedModules, ['docs-ja-jp']);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||||
|
fs.rmSync(projectDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests();
|
||||||
Reference in New Issue
Block a user