@aws/nx-plugin 1.0.0-rc.66 → 1.0.0-rc.68
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/migrations.json +11 -1
- package/package.json +1 -1
- package/src/migrations/latest/smithy-server-package-rename/metadata.json +3 -0
- package/src/migrations/latest/smithy-server-package-rename/migration.d.ts +6 -0
- package/src/migrations/latest/smithy-server-package-rename/migration.js +123 -0
- package/src/migrations/latest/smithy-server-package-rename/migration.js.map +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/build-system-prompt.ts.fixture +23 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/build-user-prompt.ts.fixture +96 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/config-path.ts.fixture +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/file-to-translate.ts.fixture +10 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/get-files-to-translate.ts.fixture +96 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/header.ts.fixture +15 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/log.ts.fixture +18 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/main-call.ts.fixture +6 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/main.ts.fixture +79 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/project-root.ts.fixture +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/run-with-concurrency.ts.fixture +57 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/scripts-dir.ts.fixture +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/files/translate-file-for-language.ts.fixture +61 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/already-migrated.grit +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/build-system-prompt.grit +6 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/build-user-prompt.grit +6 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/config-path.grit +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/file-to-translate.grit +11 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/get-files-to-translate.grit +7 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/log.grit +3 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/main-call.grit +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/main.grit +8 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/project-root.grit +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/run-with-concurrency.grit +6 -0
- package/src/migrations/latest/translate-script-whole-file-writes/grit/translate-file-for-language.grit +6 -0
- package/src/migrations/latest/translate-script-whole-file-writes/metadata.json +3 -0
- package/src/migrations/latest/translate-script-whole-file-writes/migration.d.ts +2 -0
- package/src/migrations/latest/translate-script-whole-file-writes/migration.js +103 -0
- package/src/migrations/latest/translate-script-whole-file-writes/migration.js.map +1 -0
- package/src/migrations/latest/translate-script-whole-file-writes/test-fixtures/released-script-first.ts.fixture +430 -0
- package/src/migrations/latest/translate-script-whole-file-writes/test-fixtures/released-script.ts.fixture +431 -0
- package/src/py/agent/a2a-connection/generator.d.ts +1 -1
- package/src/py/agent/gateway-connection/generator.d.ts +1 -1
- package/src/py/agent/mcp-connection/generator.d.ts +1 -1
- package/src/smithy/project/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/smithy/ts/api/files/handler.ts.template +1 -1
- package/src/smithy/ts/api/files/local-server.ts.template +1 -1
- package/src/smithy/ts/api/generator.d.ts +2 -2
- package/src/smithy/ts/api/generator.js +2 -2
- package/src/smithy/ts/api/generator.js.map +1 -1
- package/src/ts/astro-docs/files/translation/scripts/translate.ts.template +269 -148
- package/src/ts/rdb/smithy-connection/__snapshots__/generator.spec.ts.snap +6 -6
- package/src/utils/versions.d.ts +3 -3
- package/src/utils/versions.js +3 -3
- package/src/utils/versions.js.map +1 -1
package/migrations.json
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/schema",
|
|
3
3
|
"name": "@aws/nx-plugin",
|
|
4
4
|
"generators": {
|
|
5
|
+
"latest-smithy-server-package-rename": {
|
|
6
|
+
"version": "1.0.0-rc.67",
|
|
7
|
+
"description": "Move Smithy APIs onto the renamed @smithy/server-* packages",
|
|
8
|
+
"implementation": "./src/migrations/latest/smithy-server-package-rename/migration"
|
|
9
|
+
},
|
|
10
|
+
"latest-translate-script-whole-file-writes": {
|
|
11
|
+
"version": "1.0.0-rc.68",
|
|
12
|
+
"description": "Update the docs translate script to write whole translated files rather than editing them in place",
|
|
13
|
+
"implementation": "./src/migrations/latest/translate-script-whole-file-writes/migration"
|
|
14
|
+
},
|
|
5
15
|
"v1.0.0-rc.50-0001-modernize-function-props-cast": {
|
|
6
16
|
"version": "1.0.0-rc.50",
|
|
7
17
|
"description": "Replace the legacy angle-bracket FunctionProps type assertion with the modern as syntax in generated API constructs",
|
|
@@ -98,7 +108,7 @@
|
|
|
98
108
|
"implementation": "./src/migrations/v1.0.0-rc.65/0002-rolldown-code-splitting/migration"
|
|
99
109
|
},
|
|
100
110
|
"sync-vended-versions": {
|
|
101
|
-
"version": "1.0.0-rc.
|
|
111
|
+
"version": "1.0.0-rc.68",
|
|
102
112
|
"description": "Sync vended dependency versions and the tracked plugin version to those vended by this release",
|
|
103
113
|
"implementation": "./src/utils/version-upgrade-migration/migration"
|
|
104
114
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { getProjects, joinPathFragments, readJson, updateJson } from "@nx/devkit";
|
|
5
|
+
import { applyGritQL, matchGritQL } from "../../../utils/ast.js";
|
|
6
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
7
|
+
/**
|
|
8
|
+
* Move a Smithy API onto the renamed `@smithy/server-*` packages.
|
|
9
|
+
*
|
|
10
|
+
* The Smithy server runtime was published as `@aws-smithy/server-*`, stopped at
|
|
11
|
+
* `1.0.0-alpha.10`, and was renamed to `@smithy/server-*`. The Server SDK codegen
|
|
12
|
+
* this release vends generates against the renamed packages, importing
|
|
13
|
+
* `AuthScheme` and `ServerInterceptor` — types the deprecated packages never
|
|
14
|
+
* exported — so a workspace left on them fails to bundle its SSDK.
|
|
15
|
+
*
|
|
16
|
+
* Both the imports and the declarations move: the renamed packages are no longer
|
|
17
|
+
* the same names, so the version sync cannot recognise the old ones and would
|
|
18
|
+
* leave them installed alongside the new.
|
|
19
|
+
*
|
|
20
|
+
* Guardrails:
|
|
21
|
+
* - Scoped to a Smithy API project this plugin generated, found through the
|
|
22
|
+
* metadata the generator records. A file of the user's own that happens to
|
|
23
|
+
* import these packages is left alone.
|
|
24
|
+
* - Imports are rewritten by module specifier, preserving whatever the file
|
|
25
|
+
* imports from it, so a project that imports more than the generated shape
|
|
26
|
+
* keeps its own bindings. Only the two packages the generators vend are
|
|
27
|
+
* matched: `@aws-smithy/server-common` is a transitive dependency of those and
|
|
28
|
+
* is never declared or imported directly.
|
|
29
|
+
* - A manifest is only rewritten where it declares the old package, and the new
|
|
30
|
+
* name takes the field the old one occupied. A workspace that already moved
|
|
31
|
+
* itself is left alone.
|
|
32
|
+
* - Idempotent: nothing matches the old names once they are gone.
|
|
33
|
+
*
|
|
34
|
+
* Every value here is hardcoded rather than read from the generators: this runs
|
|
35
|
+
* once, for the release that renamed the packages, so it has to keep applying
|
|
36
|
+
* that exact change however far the vended versions and generators move
|
|
37
|
+
* afterwards.
|
|
38
|
+
*/ /** The id the `ts#smithy-api` generator records on the projects it creates. */ const SMITHY_API_GENERATOR_ID = 'ts#smithy-api';
|
|
39
|
+
/** The renamed packages, old name to new, with the version this release vends. */ const RENAMES = [
|
|
40
|
+
{
|
|
41
|
+
old: '@aws-smithy/server-apigateway',
|
|
42
|
+
renamed: '@smithy/server-apigateway',
|
|
43
|
+
version: '0.2.0'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
old: '@aws-smithy/server-node',
|
|
47
|
+
renamed: '@smithy/server-node',
|
|
48
|
+
version: '0.2.0'
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
/** The source files the generators give these imports. */ const SOURCE_FILES = [
|
|
52
|
+
'src/handler.ts',
|
|
53
|
+
'src/local-server.ts'
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Rewrite an import's module specifier, keeping its bindings — `$bindings` holds
|
|
57
|
+
* whatever the file imports, so a binding the project added survives the move.
|
|
58
|
+
*/ const importPattern = (from, to)=>`\`import { $bindings } from '${from}'\` => \`import { $bindings } from '${to}'\``;
|
|
59
|
+
/** Whether the file still imports the old package under any other form. */ const hasRemainingImport = (from)=>`\`import $_ from '${from}'\``;
|
|
60
|
+
/** Rename the packages a manifest declares, reporting whether anything changed. */ const renameInManifest = (tree, path)=>{
|
|
61
|
+
if (!tree.exists(path)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const json = readJson(tree, path);
|
|
65
|
+
const fields = [
|
|
66
|
+
'dependencies',
|
|
67
|
+
'devDependencies'
|
|
68
|
+
].filter((field)=>RENAMES.some(({ old })=>json[field]?.[old]));
|
|
69
|
+
if (fields.length === 0) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
updateJson(tree, path, (manifest)=>{
|
|
73
|
+
for (const field of fields){
|
|
74
|
+
const declared = manifest[field];
|
|
75
|
+
for (const { old, renamed, version } of RENAMES){
|
|
76
|
+
if (!declared[old]) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
delete declared[old];
|
|
80
|
+
// The vended pin rather than the old specifier: the renamed line
|
|
81
|
+
// restarted at 0.x, so carrying `1.0.0-alpha.10` across resolves nothing.
|
|
82
|
+
declared[renamed] = version;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return manifest;
|
|
86
|
+
});
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
const divergedNextStep = (path)=>`${path}: still imports a deprecated '@aws-smithy/server-*' package in a form this migration does not rewrite - update the import to the matching '@smithy/server-*' package by hand.`;
|
|
90
|
+
export default async function migration(tree) {
|
|
91
|
+
const nextSteps = [];
|
|
92
|
+
// Only a Smithy API project this plugin generated carries these packages.
|
|
93
|
+
const projects = [
|
|
94
|
+
...getProjects(tree).values()
|
|
95
|
+
].filter((project)=>project.metadata?.generator === SMITHY_API_GENERATOR_ID);
|
|
96
|
+
for (const project of projects){
|
|
97
|
+
for (const sourceFile of SOURCE_FILES){
|
|
98
|
+
const sourcePath = joinPathFragments(project.root, sourceFile);
|
|
99
|
+
if (!tree.exists(sourcePath)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const { old, renamed } of RENAMES){
|
|
103
|
+
await applyGritQL(tree, sourcePath, importPattern(old, renamed));
|
|
104
|
+
// A namespace or default import of the same package is left as it is —
|
|
105
|
+
// reported rather than rewritten, so a diverged file is never clobbered.
|
|
106
|
+
if (await matchGritQL(tree, sourcePath, hasRemainingImport(old))) {
|
|
107
|
+
nextSteps.push(divergedNextStep(sourcePath));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
renameInManifest(tree, joinPathFragments(project.root, 'package.json'));
|
|
112
|
+
}
|
|
113
|
+
// A workspace may hoist these to the root manifest, which is not an nx project.
|
|
114
|
+
if (projects.length > 0) {
|
|
115
|
+
renameInManifest(tree, 'package.json');
|
|
116
|
+
}
|
|
117
|
+
await formatFilesInSubtree(tree);
|
|
118
|
+
return {
|
|
119
|
+
nextSteps
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/smithy-server-package-rename/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n readJson,\n type Tree,\n updateJson,\n} from '@nx/devkit';\nimport { applyGritQL, matchGritQL } from '../../../utils/ast';\nimport { formatFilesInSubtree } from '../../../utils/format';\n\n/**\n * Move a Smithy API onto the renamed `@smithy/server-*` packages.\n *\n * The Smithy server runtime was published as `@aws-smithy/server-*`, stopped at\n * `1.0.0-alpha.10`, and was renamed to `@smithy/server-*`. The Server SDK codegen\n * this release vends generates against the renamed packages, importing\n * `AuthScheme` and `ServerInterceptor` — types the deprecated packages never\n * exported — so a workspace left on them fails to bundle its SSDK.\n *\n * Both the imports and the declarations move: the renamed packages are no longer\n * the same names, so the version sync cannot recognise the old ones and would\n * leave them installed alongside the new.\n *\n * Guardrails:\n * - Scoped to a Smithy API project this plugin generated, found through the\n * metadata the generator records. A file of the user's own that happens to\n * import these packages is left alone.\n * - Imports are rewritten by module specifier, preserving whatever the file\n * imports from it, so a project that imports more than the generated shape\n * keeps its own bindings. Only the two packages the generators vend are\n * matched: `@aws-smithy/server-common` is a transitive dependency of those and\n * is never declared or imported directly.\n * - A manifest is only rewritten where it declares the old package, and the new\n * name takes the field the old one occupied. A workspace that already moved\n * itself is left alone.\n * - Idempotent: nothing matches the old names once they are gone.\n *\n * Every value here is hardcoded rather than read from the generators: this runs\n * once, for the release that renamed the packages, so it has to keep applying\n * that exact change however far the vended versions and generators move\n * afterwards.\n */\n\n/** The id the `ts#smithy-api` generator records on the projects it creates. */\nconst SMITHY_API_GENERATOR_ID = 'ts#smithy-api';\n\n/** The renamed packages, old name to new, with the version this release vends. */\nconst RENAMES = [\n {\n old: '@aws-smithy/server-apigateway',\n renamed: '@smithy/server-apigateway',\n version: '0.2.0',\n },\n {\n old: '@aws-smithy/server-node',\n renamed: '@smithy/server-node',\n version: '0.2.0',\n },\n] as const;\n\n/** The source files the generators give these imports. */\nconst SOURCE_FILES = ['src/handler.ts', 'src/local-server.ts'];\n\n/**\n * Rewrite an import's module specifier, keeping its bindings — `$bindings` holds\n * whatever the file imports, so a binding the project added survives the move.\n */\nconst importPattern = (from: string, to: string): string =>\n `\\`import { $bindings } from '${from}'\\` => \\`import { $bindings } from '${to}'\\``;\n\n/** Whether the file still imports the old package under any other form. */\nconst hasRemainingImport = (from: string): string =>\n `\\`import $_ from '${from}'\\``;\n\n/** Rename the packages a manifest declares, reporting whether anything changed. */\nconst renameInManifest = (tree: Tree, path: string): boolean => {\n if (!tree.exists(path)) {\n return false;\n }\n const json = readJson(tree, path);\n const fields = (['dependencies', 'devDependencies'] as const).filter(\n (field) => RENAMES.some(({ old }) => json[field]?.[old]),\n );\n if (fields.length === 0) {\n return false;\n }\n updateJson(tree, path, (manifest) => {\n for (const field of fields) {\n const declared = manifest[field] as Record<string, string>;\n for (const { old, renamed, version } of RENAMES) {\n if (!declared[old]) {\n continue;\n }\n delete declared[old];\n // The vended pin rather than the old specifier: the renamed line\n // restarted at 0.x, so carrying `1.0.0-alpha.10` across resolves nothing.\n declared[renamed] = version;\n }\n }\n return manifest;\n });\n return true;\n};\n\nconst divergedNextStep = (path: string): string =>\n `${path}: still imports a deprecated '@aws-smithy/server-*' package in a form this migration does not rewrite - update the import to the matching '@smithy/server-*' package by hand.`;\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n // Only a Smithy API project this plugin generated carries these packages.\n const projects = [...getProjects(tree).values()].filter(\n (project) =>\n (project.metadata as { generator?: string } | undefined)?.generator ===\n SMITHY_API_GENERATOR_ID,\n );\n\n for (const project of projects) {\n for (const sourceFile of SOURCE_FILES) {\n const sourcePath = joinPathFragments(project.root, sourceFile);\n if (!tree.exists(sourcePath)) {\n continue;\n }\n for (const { old, renamed } of RENAMES) {\n await applyGritQL(tree, sourcePath, importPattern(old, renamed));\n // A namespace or default import of the same package is left as it is —\n // reported rather than rewritten, so a diverged file is never clobbered.\n if (await matchGritQL(tree, sourcePath, hasRemainingImport(old))) {\n nextSteps.push(divergedNextStep(sourcePath));\n }\n }\n }\n\n renameInManifest(tree, joinPathFragments(project.root, 'package.json'));\n }\n\n // A workspace may hoist these to the root manifest, which is not an nx project.\n if (projects.length > 0) {\n renameInManifest(tree, 'package.json');\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","readJson","updateJson","applyGritQL","matchGritQL","formatFilesInSubtree","SMITHY_API_GENERATOR_ID","RENAMES","old","renamed","version","SOURCE_FILES","importPattern","from","to","hasRemainingImport","renameInManifest","tree","path","exists","json","fields","filter","field","some","length","manifest","declared","divergedNextStep","migration","nextSteps","projects","values","project","metadata","generator","sourceFile","sourcePath","root","push"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function buildSystemPrompt(targetLang: string): string {
|
|
2
|
+
return `You are an expert technical-documentation translator. You translate MDX documentation files from the source locale \`${config.sourceLanguage}\` into the target locale \`${targetLang}\`.
|
|
3
|
+
|
|
4
|
+
Both are locale codes (ISO 639-1 / BCP-47 style, or short forms like \`jp\`, \`zh\`, \`pt\`). Translate naturally into the language the target code identifies.
|
|
5
|
+
|
|
6
|
+
Use the \`fileEditor\` tool to read the files you are given and to write your translation. Read a long file in slices with \`view_range\`. Use the absolute paths exactly as given — write to the target path verbatim, never to a variation of it.
|
|
7
|
+
|
|
8
|
+
Translate the prose. Everything else is copied from the source exactly as it is:
|
|
9
|
+
|
|
10
|
+
- Code blocks. Reproduce each one character for character: the language and \`title\` on the opening fence, and every line of the body. Comments inside code are code — a \`# Creates a new subsegment\` stays in English. Never translate anything between the fences.
|
|
11
|
+
- Inline code, URLs, link paths, \`import\` statements, component names, JSX attributes, and \`<Snippet name="..." />\` values.
|
|
12
|
+
- Product, service, and people's names, and package names such as \`@aws/nx-plugin\` — translate no part of them, even where they appear in prose or in a \`title\`.
|
|
13
|
+
- The MDX structure itself: frontmatter delimiters, heading levels, lists, and the blank lines between blocks. Add none and remove none.
|
|
14
|
+
|
|
15
|
+
Two things do change:
|
|
16
|
+
|
|
17
|
+
- In frontmatter, translate only \`title\` and \`description\`. Leave every other key and value alone. Frontmatter is YAML, so wrap a value in double quotes whenever it would otherwise begin with a character YAML reserves — for example a title starting with \`@aws/nx-plugin\` must be written as \`"@aws/nx-plugin ..."\`.
|
|
18
|
+
- Rewrite link paths that embed the source locale (\`/${config.sourceLanguage}/foo\` becomes \`/${targetLang}/foo\`), and make a \`parentHeading\` match the translated heading it sits under.
|
|
19
|
+
|
|
20
|
+
Write the finished file in one \`create\` call, and never with \`str_replace\` — a partial replacement silently duplicates or splices sections. Do not wrap the file in triple backticks.
|
|
21
|
+
|
|
22
|
+
When the file is written, reply with a one-line summary and stop.`;
|
|
23
|
+
}
|
package/src/migrations/latest/translate-script-whole-file-writes/files/build-user-prompt.ts.fixture
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function buildUserPrompt(file: FileToTranslate, targetLang: string): string {
|
|
2
|
+
const targetAbsPath = path.join(DOCS_DIR, targetLang, file.relativePath);
|
|
3
|
+
|
|
4
|
+
if (!file.diffPath) {
|
|
5
|
+
return `Translate \`${file.sourceAbsPath}\` into locale \`${targetLang}\` and write it to \`${targetAbsPath}\`.
|
|
6
|
+
|
|
7
|
+
Read the source, then write the whole translated file with \`create\`. Always write it, even if a translation is already there — it may be out of date.`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return `Update the \`${targetLang}\` translation of \`${file.sourceAbsPath}\`.
|
|
11
|
+
|
|
12
|
+
- Source: \`${file.sourceAbsPath}\`
|
|
13
|
+
- Existing translation to update: \`${targetAbsPath}\`
|
|
14
|
+
- Diff of what changed in the source: \`${file.diffPath}\`
|
|
15
|
+
|
|
16
|
+
Read all three. Retranslate only what the diff touched, keep the existing translated wording everywhere else, then write the whole file back with \`create\`.`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const FENCE_RE = /^\s*(?:`{3,}|~{3,})/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Split a document into alternating prose and fenced-code segments.
|
|
23
|
+
*/
|
|
24
|
+
function splitOnFences(text: string): { prose: string[]; code: string[] } {
|
|
25
|
+
const prose: string[] = [];
|
|
26
|
+
const code: string[] = [];
|
|
27
|
+
let current: string[] = [];
|
|
28
|
+
let inFence = false;
|
|
29
|
+
for (const line of text.split('\n')) {
|
|
30
|
+
if (FENCE_RE.test(line)) {
|
|
31
|
+
(inFence ? code : prose).push(current.concat(line).join('\n'));
|
|
32
|
+
current = [];
|
|
33
|
+
inFence = !inFence;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
current.push(line);
|
|
37
|
+
}
|
|
38
|
+
(inFence ? code : prose).push(current.join('\n'));
|
|
39
|
+
return { prose, code };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* YAML frontmatter must still parse after translation. A value that begins with
|
|
44
|
+
* a character YAML reserves — most often a title starting with \`@aws/...\` —
|
|
45
|
+
* breaks the docs build, so reject it here and let the retry quote it.
|
|
46
|
+
*/
|
|
47
|
+
function assertFrontmatterIsParseable(text: string): void {
|
|
48
|
+
const lines = text.split('\n');
|
|
49
|
+
if (lines[0]?.trim() !== '---') return;
|
|
50
|
+
for (let i = 1; i < lines.length && lines[i].trim() !== '---'; i++) {
|
|
51
|
+
const value = lines[i].match(/^[A-Za-z0-9_-]+:\s+(\S)/)?.[1];
|
|
52
|
+
if (value && '@*&%!|>'.includes(value)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`frontmatter line ${i + 1} starts with the reserved YAML character "${value}" and must be quoted: ${lines[i].trim()}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Copy the source's code blocks over the translated file's. The prompt tells the
|
|
62
|
+
* agent to reproduce them verbatim, but it occasionally translates a comment
|
|
63
|
+
* anyway, which would ship stale or broken code.
|
|
64
|
+
*
|
|
65
|
+
* A differing block count means the translation lost the document's shape —
|
|
66
|
+
* usually a section duplicated or dropped — so this throws rather than stitching
|
|
67
|
+
* mismatched blocks together, letting the caller retry.
|
|
68
|
+
*/
|
|
69
|
+
async function restoreCodeBlocks(
|
|
70
|
+
sourceAbsPath: string,
|
|
71
|
+
targetAbsPath: string,
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const source = splitOnFences(await fs.readFile(sourceAbsPath, 'utf-8'));
|
|
74
|
+
const target = splitOnFences(await fs.readFile(targetAbsPath, 'utf-8'));
|
|
75
|
+
|
|
76
|
+
if (source.code.length !== target.code.length) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`translation has ${target.code.length} code block(s) but the source has ${source.code.length}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (source.code.length === 0) return;
|
|
82
|
+
|
|
83
|
+
const merged = target.prose.flatMap((chunk, i) =>
|
|
84
|
+
i < source.code.length ? [chunk, source.code[i]] : [chunk],
|
|
85
|
+
);
|
|
86
|
+
await fs.writeFile(targetAbsPath, merged.join('\n'), 'utf-8');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Number of times to retry a single (file x language) translation. Large files
|
|
91
|
+
* occasionally hit transient Bedrock streaming errors; a fresh agent invocation
|
|
92
|
+
* re-reads the files from disk, so retries are idempotent.
|
|
93
|
+
*/
|
|
94
|
+
const TRANSLATE_MAX_ATTEMPTS = 4;
|
|
95
|
+
|
|
96
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const CONFIG_PATH = path.resolve(SCRIPTS_DIR, 'translate.config.json');
|
package/src/migrations/latest/translate-script-whole-file-writes/files/file-to-translate.ts.fixture
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface FileToTranslate {
|
|
2
|
+
relativePath: string;
|
|
3
|
+
sourceAbsPath: string;
|
|
4
|
+
/**
|
|
5
|
+
* Path to a file holding the source diff, or undefined when there is no prior
|
|
6
|
+
* translation to update. Passed to the agent as a path rather than inlined
|
|
7
|
+
* into the prompt, so it reads only as much of it as it needs.
|
|
8
|
+
*/
|
|
9
|
+
diffPath?: string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
async function getFilesToTranslate(): Promise<FileToTranslate[]> {
|
|
2
|
+
const sourceLangRoot = `${DOCS_DIR}/${config.sourceLanguage}`;
|
|
3
|
+
const includePatterns = config.include.map((p) => `${sourceLangRoot}/${p}`);
|
|
4
|
+
const ignorePatterns = config.exclude.map((p) => `${sourceLangRoot}/${p}`);
|
|
5
|
+
|
|
6
|
+
if (options.all) {
|
|
7
|
+
log.info('Translating all source documentation files');
|
|
8
|
+
const files = await glob(includePatterns, { ignore: ignorePatterns });
|
|
9
|
+
return files.map((file) => ({
|
|
10
|
+
relativePath: path.relative(sourceLangRoot, file),
|
|
11
|
+
sourceAbsPath: file,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const git = simpleGit();
|
|
16
|
+
// git reports paths relative to the repo root, which is not necessarily the
|
|
17
|
+
// directory this script runs from.
|
|
18
|
+
const repoRoot = (await git.revparse(['--show-toplevel'])).trim();
|
|
19
|
+
|
|
20
|
+
let currentBranch: string;
|
|
21
|
+
let mainBranch: string;
|
|
22
|
+
if (process.env.GITHUB_HEAD_REF) {
|
|
23
|
+
currentBranch = `origin/${process.env.GITHUB_HEAD_REF}`;
|
|
24
|
+
mainBranch = 'origin/main';
|
|
25
|
+
} else {
|
|
26
|
+
currentBranch = (await git.branch()).current;
|
|
27
|
+
mainBranch = 'main';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await git.raw(['rev-parse', '--verify', mainBranch]);
|
|
32
|
+
} catch {
|
|
33
|
+
log.warn(`Could not find "${mainBranch}"; falling back to --all behaviour`);
|
|
34
|
+
options.all = true;
|
|
35
|
+
return getFilesToTranslate();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const mergeBase = (
|
|
39
|
+
await git.raw(['merge-base', mainBranch, currentBranch])
|
|
40
|
+
).trim();
|
|
41
|
+
|
|
42
|
+
const translationCommits = (
|
|
43
|
+
await git.log({ from: mergeBase, to: 'HEAD' })
|
|
44
|
+
).all.filter((c) => c.message.includes(TRANSLATION_COMMIT_MESSAGE));
|
|
45
|
+
|
|
46
|
+
const baseCommit =
|
|
47
|
+
translationCommits.length > 0 ? translationCommits[0].hash : mergeBase;
|
|
48
|
+
|
|
49
|
+
log.info(
|
|
50
|
+
translationCommits.length > 0
|
|
51
|
+
? `Detecting changed files since last translation commit ${baseCommit.substring(0, 7)}`
|
|
52
|
+
: `Detecting changed files since branch creation ${baseCommit.substring(0, 7)}`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const diffNames = (
|
|
56
|
+
await git.diff([`${baseCommit}..HEAD`, '--name-only', '--diff-filter=d'])
|
|
57
|
+
)
|
|
58
|
+
.split('\n')
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
|
|
61
|
+
const { files: uncommitted } = await git.status();
|
|
62
|
+
const uncommittedNames = uncommitted.map((f) => f.path);
|
|
63
|
+
|
|
64
|
+
const allCandidates = [...new Set([...diffNames, ...uncommittedNames])].map(
|
|
65
|
+
(p) => path.resolve(repoRoot, p),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Filter to files inside the source language dir that match include/exclude
|
|
69
|
+
const includedGlob = await glob(includePatterns, {
|
|
70
|
+
ignore: ignorePatterns,
|
|
71
|
+
});
|
|
72
|
+
const includedSet = new Set(includedGlob.map((p) => path.resolve(p)));
|
|
73
|
+
|
|
74
|
+
const changed = allCandidates.filter((abs) => includedSet.has(abs));
|
|
75
|
+
|
|
76
|
+
if (changed.length === 0) {
|
|
77
|
+
log.warn('No changed source documentation files detected');
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Promise.all(
|
|
82
|
+
changed.map(async (file) => {
|
|
83
|
+
const relativePath = path.relative(sourceLangRoot, file);
|
|
84
|
+
const diff = await git.diff([
|
|
85
|
+
`${baseCommit}..HEAD`,
|
|
86
|
+
'--',
|
|
87
|
+
path.relative(repoRoot, file),
|
|
88
|
+
]);
|
|
89
|
+
return {
|
|
90
|
+
relativePath,
|
|
91
|
+
sourceAbsPath: file,
|
|
92
|
+
diffPath: diff ? await writeDiff(relativePath, diff) : undefined,
|
|
93
|
+
};
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate documentation files using a Strands agent powered by Claude on
|
|
3
|
+
* Amazon Bedrock.
|
|
4
|
+
*
|
|
5
|
+
* Configuration lives in ./translate.config.json (sibling to this file).
|
|
6
|
+
* Run from the project root:
|
|
7
|
+
*
|
|
8
|
+
* <%= pkgMgrCmd %> nx translate <%= fullyQualifiedName %> -- --all
|
|
9
|
+
* <%= pkgMgrCmd %> nx translate <%= fullyQualifiedName %> # only files changed since last translate commit
|
|
10
|
+
*
|
|
11
|
+
* The driver works out which source docs to translate and spawns one Strands
|
|
12
|
+
* agent per (file × target language). Each agent gets the `fileEditor` tool and
|
|
13
|
+
* the paths it needs — the source file, the target file, and a diff of what
|
|
14
|
+
* changed — then reads, translates and writes the file itself.
|
|
15
|
+
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const log = {
|
|
2
|
+
info: (m: string) => console.log(`[translate] ${m}`),
|
|
3
|
+
warn: (m: string) => console.warn(`[translate] ${m}`),
|
|
4
|
+
error: (m: string) => console.error(`[translate] ERROR ${m}`),
|
|
5
|
+
verbose: (m: string) => options.verbose && console.log(`[translate] ${m}`),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Diffs live under the docs directory so the agent's file access — which is
|
|
10
|
+
* scoped to that directory — can read them. Removed when the run finishes.
|
|
11
|
+
*/
|
|
12
|
+
const DIFF_DIR = path.join(DOCS_DIR, '.translate-diffs');
|
|
13
|
+
|
|
14
|
+
async function writeDiff(relativePath: string, diff: string): Promise<string> {
|
|
15
|
+
const diffPath = path.join(DIFF_DIR, `${relativePath}.diff`);
|
|
16
|
+
await fs.outputFile(diffPath, diff, 'utf-8');
|
|
17
|
+
return diffPath;
|
|
18
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
async function main() {
|
|
2
|
+
const requestedLanguages: string[] = options.languages
|
|
3
|
+
? options.languages
|
|
4
|
+
.split(',')
|
|
5
|
+
.map((l: string) => l.trim())
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
: config.targetLanguages;
|
|
8
|
+
|
|
9
|
+
const targetLanguages = requestedLanguages.filter(
|
|
10
|
+
(l) => l !== config.sourceLanguage,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
if (targetLanguages.length === 0) {
|
|
14
|
+
log.error('No target languages configured');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
log.info(`Source: ${config.sourceLanguage}`);
|
|
19
|
+
log.info(`Targets: ${targetLanguages.join(', ')}`);
|
|
20
|
+
|
|
21
|
+
await pruneOrphanedTranslations(targetLanguages);
|
|
22
|
+
|
|
23
|
+
const files = await getFilesToTranslate();
|
|
24
|
+
|
|
25
|
+
if (files.length === 0) {
|
|
26
|
+
log.info('No documentation files to translate.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
log.info(`Files to translate: ${files.length}`);
|
|
31
|
+
for (const f of files) {
|
|
32
|
+
log.verbose(
|
|
33
|
+
` - ${f.relativePath}${f.diffPath ? ' (changed)' : ' (full)'}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (options.dryRun) {
|
|
38
|
+
for (const lang of targetLanguages) {
|
|
39
|
+
for (const f of files) {
|
|
40
|
+
log.info(`[dry-run] would translate ${f.relativePath} → ${lang}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
log.info('Done (dry-run).');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Build one task per (file × target language) — each is a fresh agent invocation,
|
|
48
|
+
// so context windows stay small no matter how big the docs site is. A single
|
|
49
|
+
// failing translation records its error rather than aborting the whole run, so
|
|
50
|
+
// every other (file × language) still completes and gets written to disk.
|
|
51
|
+
const failures: string[] = [];
|
|
52
|
+
const tasks = targetLanguages.flatMap((lang) =>
|
|
53
|
+
files.map((file) => async () => {
|
|
54
|
+
log.info(` ${file.relativePath} → ${lang}`);
|
|
55
|
+
try {
|
|
56
|
+
await translateFileForLanguage(file, lang);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
log.error(
|
|
59
|
+
`${file.relativePath} → ${lang}: ${err instanceof Error ? err.message : String(err)}`,
|
|
60
|
+
);
|
|
61
|
+
failures.push(`${file.relativePath} → ${lang}`);
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const concurrency = Math.max(1, config.concurrency ?? 5);
|
|
67
|
+
log.info(
|
|
68
|
+
`Running ${tasks.length} translation(s) with concurrency=${concurrency}`,
|
|
69
|
+
);
|
|
70
|
+
await runWithConcurrency(tasks, concurrency);
|
|
71
|
+
|
|
72
|
+
if (failures.length > 0) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`${failures.length} translation(s) failed after retries:\n ${failures.join('\n ')}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log.info('Done.');
|
|
79
|
+
}
|
package/src/migrations/latest/translate-script-whole-file-writes/files/project-root.ts.fixture
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const PROJECT_ROOT = path.resolve(SCRIPTS_DIR, '..');
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
async function runWithConcurrency<T>(
|
|
2
|
+
tasks: Array<() => Promise<T>>,
|
|
3
|
+
limit: number,
|
|
4
|
+
): Promise<T[]> {
|
|
5
|
+
const results: T[] = new Array(tasks.length);
|
|
6
|
+
let next = 0;
|
|
7
|
+
async function worker() {
|
|
8
|
+
while (true) {
|
|
9
|
+
const i = next++;
|
|
10
|
+
if (i >= tasks.length) return;
|
|
11
|
+
results[i] = await tasks[i]();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const workers = Array.from({ length: Math.min(limit, tasks.length) }, worker);
|
|
15
|
+
await Promise.all(workers);
|
|
16
|
+
return results;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Delete translated files whose source document no longer exists. Without this
|
|
21
|
+
* a source file that is renamed or removed leaves its translations behind in
|
|
22
|
+
* every locale, where they still resolve as snippets and pages.
|
|
23
|
+
*/
|
|
24
|
+
async function pruneOrphanedTranslations(
|
|
25
|
+
targetLanguages: string[],
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const sourceLangRoot = `${DOCS_DIR}/${config.sourceLanguage}`;
|
|
28
|
+
const sourceFiles = new Set(
|
|
29
|
+
(
|
|
30
|
+
await glob(
|
|
31
|
+
config.include.map((p) => `${sourceLangRoot}/${p}`),
|
|
32
|
+
{ ignore: config.exclude.map((p) => `${sourceLangRoot}/${p}`) },
|
|
33
|
+
)
|
|
34
|
+
).map((p) => path.relative(sourceLangRoot, p)),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
for (const lang of targetLanguages) {
|
|
38
|
+
const langRoot = path.join(DOCS_DIR, lang);
|
|
39
|
+
if (!fs.existsSync(langRoot)) continue;
|
|
40
|
+
const translated = await glob(
|
|
41
|
+
config.include.map((p) => `${langRoot}/${p}`),
|
|
42
|
+
{ ignore: config.exclude.map((p) => `${langRoot}/${p}`) },
|
|
43
|
+
);
|
|
44
|
+
for (const abs of translated) {
|
|
45
|
+
const rel = path.relative(langRoot, abs);
|
|
46
|
+
if (sourceFiles.has(rel)) continue;
|
|
47
|
+
if (options.dryRun) {
|
|
48
|
+
log.info(`[dry-run] would delete orphaned ${lang}/${rel}`);
|
|
49
|
+
} else {
|
|
50
|
+
log.info(
|
|
51
|
+
`Deleting orphaned ${lang}/${rel} (no ${config.sourceLanguage} source)`,
|
|
52
|
+
);
|
|
53
|
+
await fs.remove(abs);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const SCRIPTS_DIR = <% if (esm) { %>import.meta.dirname<% } else { %>__dirname<% } %>;
|