toolfactory 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/commands.d.ts +1 -1
- package/dist/distribution/native.d.ts +50 -0
- package/dist/distribution/native.js +414 -0
- package/dist/distribution/native.test.d.ts +1 -0
- package/dist/distribution/native.test.js +127 -0
- package/dist/distribution/system.d.ts +57 -0
- package/dist/distribution/system.js +637 -0
- package/dist/distribution/system.test.d.ts +1 -0
- package/dist/distribution/system.test.js +116 -0
- package/dist/e2e.test.js +7 -1
- package/dist/hosts/openclaw.js +6 -2
- package/dist/hosts/vscode.d.ts +1 -0
- package/dist/hosts/vscode.js +67 -0
- package/dist/model.d.ts +62 -2
- package/dist/model.js +9 -0
- package/dist/ops.d.ts +52 -0
- package/dist/project/apply.d.ts +1 -1
- package/dist/project/apply.js +194 -17
- package/dist/project/apply.test.js +103 -0
- package/dist/project/gate.d.ts +17 -2
- package/dist/project/gate.js +68 -3
- package/dist/project/history.d.ts +29 -1
- package/dist/project/lock.d.ts +5 -0
- package/dist/project/lock.js +9 -0
- package/dist/surfaces/agent-plugins.js +5 -4
- package/dist/surfaces/agents.js +21 -3
- package/dist/surfaces/agents.test.js +12 -0
- package/dist/surfaces/claude.js +8 -3
- package/dist/surfaces/codex.js +8 -3
- package/dist/surfaces/codex.test.js +8 -8
- package/dist/surfaces/cursor.js +2 -2
- package/dist/surfaces/distribution.test.js +3 -3
- package/dist/surfaces/dsh.js +25 -6
- package/dist/surfaces/dsh.test.js +8 -3
- package/dist/surfaces/gemini.js +2 -2
- package/dist/surfaces/gemini.test.js +4 -4
- package/dist/surfaces/hermes-native.js +38 -9
- package/dist/surfaces/hermes-native.test.js +32 -11
- package/dist/surfaces/mcp-registry.js +43 -5
- package/dist/surfaces/mcp-registry.test.js +18 -0
- package/dist/surfaces/openclaw-native.js +10 -8
- package/dist/surfaces/openclaw-native.test.js +30 -0
- package/dist/surfaces/readme.d.ts +16 -0
- package/dist/surfaces/readme.js +7 -0
- package/dist/surfaces/registry.js +6 -0
- package/dist/surfaces/vscode-extension.d.ts +14 -0
- package/dist/surfaces/vscode-extension.js +312 -0
- package/dist/surfaces/vscode-extension.test.d.ts +1 -0
- package/dist/surfaces/vscode-extension.test.js +81 -0
- package/dist/surfaces/vscode-scaffold.d.ts +43 -0
- package/dist/surfaces/vscode-scaffold.js +43 -0
- package/dist/surfaces/workflows.js +49 -10
- package/dist/surfaces/workflows.test.js +3 -2
- package/dist/toolfactory/cli.js +1 -1
- package/dist/toolfactory/mcp.js +1 -1
- package/package.json +2 -1
- package/src/distribution/native.test.ts +161 -0
- package/src/distribution/native.ts +460 -0
- package/src/distribution/system.test.ts +136 -0
- package/src/distribution/system.ts +688 -0
- package/src/e2e.test.ts +7 -1
- package/src/hosts/openclaw.ts +4 -2
- package/src/hosts/vscode.ts +79 -0
- package/src/model.ts +15 -1
- package/src/project/apply.test.ts +120 -0
- package/src/project/apply.ts +257 -15
- package/src/project/gate.ts +96 -4
- package/src/project/lock.ts +12 -0
- package/src/surfaces/agent-plugins.ts +5 -4
- package/src/surfaces/agents.test.ts +16 -0
- package/src/surfaces/agents.ts +31 -3
- package/src/surfaces/claude.ts +7 -3
- package/src/surfaces/codex.test.ts +7 -7
- package/src/surfaces/codex.ts +8 -3
- package/src/surfaces/cursor.ts +2 -10
- package/src/surfaces/distribution.test.ts +2 -2
- package/src/surfaces/dsh.test.ts +8 -2
- package/src/surfaces/dsh.ts +26 -6
- package/src/surfaces/gemini.test.ts +3 -3
- package/src/surfaces/gemini.ts +1 -2
- package/src/surfaces/hermes-native.test.ts +39 -10
- package/src/surfaces/hermes-native.ts +41 -11
- package/src/surfaces/mcp-registry.test.ts +20 -0
- package/src/surfaces/mcp-registry.ts +46 -6
- package/src/surfaces/openclaw-native.test.ts +37 -0
- package/src/surfaces/openclaw-native.ts +49 -47
- package/src/surfaces/readme.ts +10 -0
- package/src/surfaces/registry.ts +6 -0
- package/src/surfaces/vscode-extension.test.ts +89 -0
- package/src/surfaces/vscode-extension.ts +345 -0
- package/src/surfaces/vscode-scaffold.ts +55 -0
- package/src/surfaces/workflows.test.ts +3 -4
- package/src/surfaces/workflows.ts +49 -10
- package/src/toolfactory/cli.ts +1 -1
- package/src/toolfactory/mcp.ts +1 -1
- package/web/dist/assets/{index-Spapkkvh.js → index-CVm6m4s0.js} +1 -1
- package/web/dist/index.html +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Captured by running generator-code@1.12.0 (TypeScript/esbuild); hosts/vscode.ts verifies it. */
|
|
2
|
+
export declare const VSCODE_GENERATOR = "1.12.0";
|
|
3
|
+
export declare const GITIGNORE = "out\ndist\nnode_modules\n.vscode-test/\n*.vsix\n";
|
|
4
|
+
export declare const VSCODE_SCAFFOLD: {
|
|
5
|
+
readonly engines: {
|
|
6
|
+
readonly vscode: "^1.136.0";
|
|
7
|
+
};
|
|
8
|
+
readonly scripts: {
|
|
9
|
+
readonly "vscode:prepublish": "npm run package";
|
|
10
|
+
readonly compile: "npm run check-types && npm run lint && node esbuild.js";
|
|
11
|
+
readonly watch: "npm-run-all -p watch:*";
|
|
12
|
+
readonly "watch:esbuild": "node esbuild.js --watch";
|
|
13
|
+
readonly "watch:tsc": "tsc --noEmit --watch --project tsconfig.json";
|
|
14
|
+
readonly package: "npm run check-types && npm run lint && node esbuild.js --production";
|
|
15
|
+
readonly "compile-tests": "tsc -p . --outDir out";
|
|
16
|
+
readonly "watch-tests": "tsc -p . -w --outDir out";
|
|
17
|
+
readonly pretest: "npm run compile-tests && npm run compile && npm run lint";
|
|
18
|
+
readonly "check-types": "tsc --noEmit";
|
|
19
|
+
readonly lint: "eslint src";
|
|
20
|
+
readonly test: "vscode-test";
|
|
21
|
+
};
|
|
22
|
+
readonly devDependencies: {
|
|
23
|
+
readonly "@types/vscode": "^1.136.0";
|
|
24
|
+
readonly "@types/mocha": "^10.0.10";
|
|
25
|
+
readonly "@types/node": "24.x";
|
|
26
|
+
readonly "typescript-eslint": "^8.61.1";
|
|
27
|
+
readonly eslint: "^10.5.0";
|
|
28
|
+
readonly esbuild: "^0.28.1";
|
|
29
|
+
readonly "npm-run-all": "^4.1.5";
|
|
30
|
+
readonly typescript: "^6.0.3";
|
|
31
|
+
readonly "@vscode/test-cli": "^0.0.15";
|
|
32
|
+
readonly "@vscode/test-electron": "^3.0.0";
|
|
33
|
+
};
|
|
34
|
+
readonly overrides: {
|
|
35
|
+
readonly diff: "^8.0.4";
|
|
36
|
+
readonly "serialize-javascript": "^7.0.6";
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export declare const ESBUILD = "const esbuild = require(\"esbuild\");\n\nconst production = process.argv.includes('--production');\nconst watch = process.argv.includes('--watch');\n\n/**\n * @type {import('esbuild').Plugin}\n */\nconst esbuildProblemMatcherPlugin = {\n\tname: 'esbuild-problem-matcher',\n\n\tsetup(build) {\n\t\tbuild.onStart(() => {\n\t\t\tconsole.log('[watch] build started');\n\t\t});\n\t\tbuild.onEnd((result) => {\n\t\t\tresult.errors.forEach(({ text, location }) => {\n\t\t\t\tconsole.error(`\u2718 [ERROR] ${text}`);\n\t\t\t\tconsole.error(` ${location.file}:${location.line}:${location.column}:`);\n\t\t\t});\n\t\t\tconsole.log('[watch] build finished');\n\t\t});\n\t},\n};\n\nasync function main() {\n\tconst ctx = await esbuild.context({\n\t\tentryPoints: [\n\t\t\t'src/extension.ts'\n\t\t],\n\t\tbundle: true,\n\t\tformat: 'cjs',\n\t\tminify: production,\n\t\tsourcemap: !production,\n\t\tsourcesContent: false,\n\t\tplatform: 'node',\n\t\toutfile: 'dist/extension.js',\n\t\texternal: ['vscode'],\n\t\tlogLevel: 'silent',\n\t\tplugins: [\n\t\t\t/* add to the end of plugins array */\n\t\t\tesbuildProblemMatcherPlugin,\n\t\t],\n\t});\n\tif (watch) {\n\t\tawait ctx.watch();\n\t} else {\n\t\tawait ctx.rebuild();\n\t\tawait ctx.dispose();\n\t}\n}\n\nmain().catch(e => {\n\tconsole.error(e);\n\tprocess.exit(1);\n});\n";
|
|
40
|
+
export declare const ESLINT = "import typescriptEslint from \"typescript-eslint\";\n\nexport default [{\n files: [\"**/*.ts\"],\n}, {\n plugins: {\n \"@typescript-eslint\": typescriptEslint.plugin,\n },\n\n languageOptions: {\n parser: typescriptEslint.parser,\n ecmaVersion: 2022,\n sourceType: \"module\",\n },\n\n rules: {\n \"@typescript-eslint/naming-convention\": [\"warn\", {\n selector: \"import\",\n format: [\"camelCase\", \"PascalCase\"],\n }],\n\n curly: \"warn\",\n eqeqeq: \"warn\",\n \"no-throw-literal\": \"warn\",\n semi: \"warn\",\n },\n}];";
|
|
41
|
+
export declare const IGNORE = ".vscode/**\n.vscode-test/**\nout/**\nnode_modules/**\nsrc/**\n.gitignore\n.yarnrc\nesbuild.js\nvsc-extension-quickstart.md\n**/tsconfig.json\n**/eslint.config.mjs\n**/*.map\n**/*.ts\n**/.vscode-test.*\n";
|
|
42
|
+
export declare const LAUNCH = "// A launch configuration that compiles the extension and then opens it inside a new window\n// Use IntelliSense to learn about possible attributes.\n// Hover to view descriptions of existing attributes.\n// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n{\n\t\"version\": \"0.2.0\",\n\t\"configurations\": [\n\t\t{\n\t\t\t\"name\": \"Run Extension\",\n\t\t\t\"type\": \"extensionHost\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"args\": [\n\t\t\t\t\"--extensionDevelopmentPath=${workspaceFolder}\"\n\t\t\t],\n\t\t\t\"outFiles\": [\n\t\t\t\t\"${workspaceFolder}/dist/**/*.js\"\n\t\t\t],\n\t\t\t\"preLaunchTask\": \"${defaultBuildTask}\"\n\t\t}\n\t]\n}\n";
|
|
43
|
+
export declare const TASKS = "// See https://go.microsoft.com/fwlink/?LinkId=733558\n// for the documentation about the tasks.json format\n{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": [\n\t\t{\n \"label\": \"watch\",\n \"dependsOn\": [\n \"npm: watch:tsc\",\n \"npm: watch:esbuild\"\n ],\n \"presentation\": {\n \"reveal\": \"never\"\n },\n \"group\": {\n \"kind\": \"build\",\n \"isDefault\": true\n }\n },\n {\n \"type\": \"npm\",\n \"script\": \"watch:esbuild\",\n \"group\": \"build\",\n \"problemMatcher\": \"$esbuild-watch\",\n \"isBackground\": true,\n \"label\": \"npm: watch:esbuild\",\n \"presentation\": {\n \"group\": \"watch\",\n \"reveal\": \"never\"\n }\n },\n\t\t{\n \"type\": \"npm\",\n \"script\": \"watch:tsc\",\n \"group\": \"build\",\n \"problemMatcher\": \"$tsc-watch\",\n \"isBackground\": true,\n \"label\": \"npm: watch:tsc\",\n \"presentation\": {\n \"group\": \"watch\",\n \"reveal\": \"never\"\n }\n },\n\t\t{\n\t\t\t\"type\": \"npm\",\n\t\t\t\"script\": \"watch-tests\",\n\t\t\t\"problemMatcher\": \"$tsc-watch\",\n\t\t\t\"isBackground\": true,\n\t\t\t\"presentation\": {\n\t\t\t\t\"reveal\": \"never\",\n\t\t\t\t\"group\": \"watchers\"\n\t\t\t},\n\t\t\t\"group\": \"build\"\n\t\t},\n\t\t{\n\t\t\t\"label\": \"tasks: watch-tests\",\n\t\t\t\"dependsOn\": [\n\t\t\t\t\"npm: watch\",\n\t\t\t\t\"npm: watch-tests\"\n\t\t\t],\n\t\t\t\"problemMatcher\": []\n\t\t}\n\t]\n}\n";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Captured by running generator-code@1.12.0 (TypeScript/esbuild); hosts/vscode.ts verifies it. */
|
|
2
|
+
export const VSCODE_GENERATOR = "1.12.0";
|
|
3
|
+
export const GITIGNORE = "out\ndist\nnode_modules\n.vscode-test/\n*.vsix\n";
|
|
4
|
+
export const VSCODE_SCAFFOLD = {
|
|
5
|
+
engines: {
|
|
6
|
+
vscode: "^1.136.0",
|
|
7
|
+
},
|
|
8
|
+
scripts: {
|
|
9
|
+
"vscode:prepublish": "npm run package",
|
|
10
|
+
compile: "npm run check-types && npm run lint && node esbuild.js",
|
|
11
|
+
watch: "npm-run-all -p watch:*",
|
|
12
|
+
"watch:esbuild": "node esbuild.js --watch",
|
|
13
|
+
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
|
14
|
+
package: "npm run check-types && npm run lint && node esbuild.js --production",
|
|
15
|
+
"compile-tests": "tsc -p . --outDir out",
|
|
16
|
+
"watch-tests": "tsc -p . -w --outDir out",
|
|
17
|
+
pretest: "npm run compile-tests && npm run compile && npm run lint",
|
|
18
|
+
"check-types": "tsc --noEmit",
|
|
19
|
+
lint: "eslint src",
|
|
20
|
+
test: "vscode-test",
|
|
21
|
+
},
|
|
22
|
+
devDependencies: {
|
|
23
|
+
"@types/vscode": "^1.136.0",
|
|
24
|
+
"@types/mocha": "^10.0.10",
|
|
25
|
+
"@types/node": "24.x",
|
|
26
|
+
"typescript-eslint": "^8.61.1",
|
|
27
|
+
eslint: "^10.5.0",
|
|
28
|
+
esbuild: "^0.28.1",
|
|
29
|
+
"npm-run-all": "^4.1.5",
|
|
30
|
+
typescript: "^6.0.3",
|
|
31
|
+
"@vscode/test-cli": "^0.0.15",
|
|
32
|
+
"@vscode/test-electron": "^3.0.0",
|
|
33
|
+
},
|
|
34
|
+
overrides: {
|
|
35
|
+
diff: "^8.0.4",
|
|
36
|
+
"serialize-javascript": "^7.0.6",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export const ESBUILD = "const esbuild = require(\"esbuild\");\n\nconst production = process.argv.includes('--production');\nconst watch = process.argv.includes('--watch');\n\n/**\n * @type {import('esbuild').Plugin}\n */\nconst esbuildProblemMatcherPlugin = {\n\tname: 'esbuild-problem-matcher',\n\n\tsetup(build) {\n\t\tbuild.onStart(() => {\n\t\t\tconsole.log('[watch] build started');\n\t\t});\n\t\tbuild.onEnd((result) => {\n\t\t\tresult.errors.forEach(({ text, location }) => {\n\t\t\t\tconsole.error(`\u2718 [ERROR] ${text}`);\n\t\t\t\tconsole.error(` ${location.file}:${location.line}:${location.column}:`);\n\t\t\t});\n\t\t\tconsole.log('[watch] build finished');\n\t\t});\n\t},\n};\n\nasync function main() {\n\tconst ctx = await esbuild.context({\n\t\tentryPoints: [\n\t\t\t'src/extension.ts'\n\t\t],\n\t\tbundle: true,\n\t\tformat: 'cjs',\n\t\tminify: production,\n\t\tsourcemap: !production,\n\t\tsourcesContent: false,\n\t\tplatform: 'node',\n\t\toutfile: 'dist/extension.js',\n\t\texternal: ['vscode'],\n\t\tlogLevel: 'silent',\n\t\tplugins: [\n\t\t\t/* add to the end of plugins array */\n\t\t\tesbuildProblemMatcherPlugin,\n\t\t],\n\t});\n\tif (watch) {\n\t\tawait ctx.watch();\n\t} else {\n\t\tawait ctx.rebuild();\n\t\tawait ctx.dispose();\n\t}\n}\n\nmain().catch(e => {\n\tconsole.error(e);\n\tprocess.exit(1);\n});\n";
|
|
40
|
+
export const ESLINT = 'import typescriptEslint from "typescript-eslint";\n\nexport default [{\n files: ["**/*.ts"],\n}, {\n plugins: {\n "@typescript-eslint": typescriptEslint.plugin,\n },\n\n languageOptions: {\n parser: typescriptEslint.parser,\n ecmaVersion: 2022,\n sourceType: "module",\n },\n\n rules: {\n "@typescript-eslint/naming-convention": ["warn", {\n selector: "import",\n format: ["camelCase", "PascalCase"],\n }],\n\n curly: "warn",\n eqeqeq: "warn",\n "no-throw-literal": "warn",\n semi: "warn",\n },\n}];';
|
|
41
|
+
export const IGNORE = ".vscode/**\n.vscode-test/**\nout/**\nnode_modules/**\nsrc/**\n.gitignore\n.yarnrc\nesbuild.js\nvsc-extension-quickstart.md\n**/tsconfig.json\n**/eslint.config.mjs\n**/*.map\n**/*.ts\n**/.vscode-test.*\n";
|
|
42
|
+
export const LAUNCH = '// A launch configuration that compiles the extension and then opens it inside a new window\n// Use IntelliSense to learn about possible attributes.\n// Hover to view descriptions of existing attributes.\n// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n{\n\t"version": "0.2.0",\n\t"configurations": [\n\t\t{\n\t\t\t"name": "Run Extension",\n\t\t\t"type": "extensionHost",\n\t\t\t"request": "launch",\n\t\t\t"args": [\n\t\t\t\t"--extensionDevelopmentPath=${workspaceFolder}"\n\t\t\t],\n\t\t\t"outFiles": [\n\t\t\t\t"${workspaceFolder}/dist/**/*.js"\n\t\t\t],\n\t\t\t"preLaunchTask": "${defaultBuildTask}"\n\t\t}\n\t]\n}\n';
|
|
43
|
+
export const TASKS = '// See https://go.microsoft.com/fwlink/?LinkId=733558\n// for the documentation about the tasks.json format\n{\n\t"version": "2.0.0",\n\t"tasks": [\n\t\t{\n "label": "watch",\n "dependsOn": [\n "npm: watch:tsc",\n "npm: watch:esbuild"\n ],\n "presentation": {\n "reveal": "never"\n },\n "group": {\n "kind": "build",\n "isDefault": true\n }\n },\n {\n "type": "npm",\n "script": "watch:esbuild",\n "group": "build",\n "problemMatcher": "$esbuild-watch",\n "isBackground": true,\n "label": "npm: watch:esbuild",\n "presentation": {\n "group": "watch",\n "reveal": "never"\n }\n },\n\t\t{\n "type": "npm",\n "script": "watch:tsc",\n "group": "build",\n "problemMatcher": "$tsc-watch",\n "isBackground": true,\n "label": "npm: watch:tsc",\n "presentation": {\n "group": "watch",\n "reveal": "never"\n }\n },\n\t\t{\n\t\t\t"type": "npm",\n\t\t\t"script": "watch-tests",\n\t\t\t"problemMatcher": "$tsc-watch",\n\t\t\t"isBackground": true,\n\t\t\t"presentation": {\n\t\t\t\t"reveal": "never",\n\t\t\t\t"group": "watchers"\n\t\t\t},\n\t\t\t"group": "build"\n\t\t},\n\t\t{\n\t\t\t"label": "tasks: watch-tests",\n\t\t\t"dependsOn": [\n\t\t\t\t"npm: watch",\n\t\t\t\t"npm: watch-tests"\n\t\t\t],\n\t\t\t"problemMatcher": []\n\t\t}\n\t]\n}\n';
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { stringify as yamlStringify } from "yaml";
|
|
21
21
|
import { LIVE_TEST_COMMAND } from "../bindings/python.js";
|
|
22
|
+
import { nativePublishJobs } from "../distribution/native.js";
|
|
23
|
+
import { systemPackageJobs, systemPublishJobs } from "../distribution/system.js";
|
|
22
24
|
import { githubOwner } from "../identity/name.js";
|
|
23
25
|
import { isInstructionOnly, } from "../model.js";
|
|
24
26
|
import { bootstrapSteps, gateSteps, gateVariable, MCP_PUBLISHER_FETCH, npmTarball, openclawTarball, outputsStep, PACKAGE_MANAGER_COMMANDS, packageSteps, RELEASE_DIR, RELEASE_SECRET_NAMES, registries, tagVersionAssert, toolfactoryCli, unpublishStep, WEB_BUILD, } from "../project/gate.js";
|
|
@@ -28,6 +30,7 @@ import { ociImage, registryName } from "./mcp-registry.js";
|
|
|
28
30
|
import { HOST_DIR as OPENCLAW_HOST_DIR } from "./openclaw-native.js";
|
|
29
31
|
import { compact, configProperties, envName, has, isSensitive, liveCredentials, npmName, requiredConfig, } from "./shared.js";
|
|
30
32
|
import { skillPath } from "./skill.js";
|
|
33
|
+
import { OVSX_VERSION, VSCE_VERSION, vsixName } from "./vscode-extension.js";
|
|
31
34
|
const YAML_HEADER = "# Generated by toolfactory. Do not edit; run `toolfactory build` to regenerate.\n\n";
|
|
32
35
|
/** The one artifact the `package` job uploads and every later job reads. */
|
|
33
36
|
const RELEASE_ARTIFACT = "release-assets";
|
|
@@ -182,6 +185,7 @@ function ciDocument(project) {
|
|
|
182
185
|
const node = typescript ? "${{ matrix.node-version }}" : "24";
|
|
183
186
|
const job = compact({
|
|
184
187
|
"runs-on": "ubuntu-latest",
|
|
188
|
+
env: { TOOLFACTORY_SYSTEM_NATIVE_JOBS: "true" },
|
|
185
189
|
strategy: typescript ? { matrix: { "node-version": ["22", "24"] } } : undefined,
|
|
186
190
|
steps: [
|
|
187
191
|
...toolchainSteps(project, node),
|
|
@@ -199,7 +203,7 @@ function ciDocument(project) {
|
|
|
199
203
|
workflow_dispatch: live ? {} : undefined,
|
|
200
204
|
}),
|
|
201
205
|
permissions: { contents: "read" },
|
|
202
|
-
jobs: compact({ test: job, live }),
|
|
206
|
+
jobs: compact({ test: job, live, ...systemPackageJobs(project, "${{ github.sha }}") }),
|
|
203
207
|
};
|
|
204
208
|
}
|
|
205
209
|
// ---------------------------------------------------------------------------------------------
|
|
@@ -362,11 +366,15 @@ function releaseDocument(project) {
|
|
|
362
366
|
};
|
|
363
367
|
const cli = toolfactoryCli(project);
|
|
364
368
|
const assert = tagVersionAssert(project);
|
|
369
|
+
const systemJobs = systemPackageJobs(project, RELEASE_SHA);
|
|
370
|
+
for (const job of Object.values(systemJobs))
|
|
371
|
+
job.needs = "gate";
|
|
365
372
|
const jobs = {
|
|
373
|
+
...systemJobs,
|
|
366
374
|
gate: compact({
|
|
367
375
|
"runs-on": "ubuntu-latest",
|
|
368
376
|
permissions: compact({ contents: "read", pages: has(project, "web") ? "read" : undefined }),
|
|
369
|
-
env: { RELEASE_TAG: TAG_NAME },
|
|
377
|
+
env: { RELEASE_TAG: TAG_NAME, TOOLFACTORY_SYSTEM_NATIVE_JOBS: "true" },
|
|
370
378
|
outputs: { sha: "${{ steps.tag.outputs.sha }}", ...presenceOutputs(rows) },
|
|
371
379
|
steps: [
|
|
372
380
|
// The whole history: the tag may exist (a re-run) or not yet (a dispatch cuts it), and
|
|
@@ -385,7 +393,7 @@ function releaseDocument(project) {
|
|
|
385
393
|
],
|
|
386
394
|
}),
|
|
387
395
|
package: compact({
|
|
388
|
-
needs: "gate",
|
|
396
|
+
needs: needsOf(["gate", ...Object.keys(systemJobs)]),
|
|
389
397
|
"runs-on": "ubuntu-latest",
|
|
390
398
|
permissions: { contents: "read" },
|
|
391
399
|
// The Firefox self-hosted xpi packageSteps emits (gate.ts) is opt-in on this pair: present,
|
|
@@ -398,7 +406,11 @@ function releaseDocument(project) {
|
|
|
398
406
|
: undefined,
|
|
399
407
|
steps: [
|
|
400
408
|
...toolchainSteps(project, "24", RELEASE_SHA),
|
|
401
|
-
...packageSteps(project).map((step) => actionStep(step, false)),
|
|
409
|
+
...packageSteps(project, { system: false }).map((step) => actionStep(step, false)),
|
|
410
|
+
...Object.keys(systemJobs).map((name) => ({
|
|
411
|
+
uses: "actions/download-artifact@v8",
|
|
412
|
+
with: { name, path: `${RELEASE_DIR}/system/${name.replace(/^system-/, "")}` },
|
|
413
|
+
})),
|
|
402
414
|
{
|
|
403
415
|
uses: "actions/upload-artifact@v7",
|
|
404
416
|
with: { name: RELEASE_ARTIFACT, path: `${RELEASE_DIR}/` },
|
|
@@ -406,7 +418,10 @@ function releaseDocument(project) {
|
|
|
406
418
|
],
|
|
407
419
|
}),
|
|
408
420
|
};
|
|
409
|
-
|
|
421
|
+
Object.assign(jobs, systemPublishJobs(project, RELEASE_ARTIFACT, RELEASE_SHA));
|
|
422
|
+
const nativeJobs = nativePublishJobs(project, RELEASE_ARTIFACT, RELEASE_SHA);
|
|
423
|
+
Object.assign(jobs, nativeJobs);
|
|
424
|
+
const priorLegs = Object.keys(nativeJobs);
|
|
410
425
|
if (npmSelected) {
|
|
411
426
|
// npm treats a slash-containing bare operand as a Git spec; make the downloaded archive a
|
|
412
427
|
// filesystem path explicitly.
|
|
@@ -457,7 +472,7 @@ function releaseDocument(project) {
|
|
|
457
472
|
{
|
|
458
473
|
name: "Publish package distributions to PyPI",
|
|
459
474
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
460
|
-
with: { "packages-dir": `${RELEASE_ARTIFACT}/pypi
|
|
475
|
+
with: { "packages-dir": `${RELEASE_ARTIFACT}/pypi/`, "skip-existing": true },
|
|
461
476
|
},
|
|
462
477
|
],
|
|
463
478
|
});
|
|
@@ -585,6 +600,33 @@ function releaseDocument(project) {
|
|
|
585
600
|
comments.push("", "publish-browser-ext-safari (opt-in, tool.json browserExtension.safari) is the macOS", "host-native leg: xcrun safari-web-extension-converter, then an Xcode archive/export", "signed with an App Store Connect API key. Runs on macos-latest, needs only `gate`, and", "does not gate the release — it submits to App Store Connect, not dist/release/.");
|
|
586
601
|
}
|
|
587
602
|
}
|
|
603
|
+
for (const id of ["vscode-marketplace", "open-vsx"]) {
|
|
604
|
+
const row = rows.find((entry) => entry.id === id);
|
|
605
|
+
if (!row)
|
|
606
|
+
continue;
|
|
607
|
+
const file = `./${RELEASE_ARTIFACT}/${vsixName(project)}`;
|
|
608
|
+
const publish = id === "vscode-marketplace"
|
|
609
|
+
? `npx --yes @vscode/vsce@${VSCE_VERSION} publish --packagePath ${file} --skip-duplicate`
|
|
610
|
+
: `npx --yes ovsx@${OVSX_VERSION} publish ${file} --skip-duplicate`;
|
|
611
|
+
const job = `publish-${id}`;
|
|
612
|
+
const runtimeJob = project.tool.binding === "python" ? "publish-pypi" : "publish-npm";
|
|
613
|
+
jobs[job] = {
|
|
614
|
+
needs: ["gate", "package", runtimeJob],
|
|
615
|
+
if: `!cancelled() && !contains(needs.*.result, 'failure') && ${gated(id)}`,
|
|
616
|
+
"runs-on": "ubuntu-latest",
|
|
617
|
+
permissions: { contents: "read" },
|
|
618
|
+
env: Object.fromEntries(row.secrets.map((key) => [key, `\${{ secrets.${key} }}`])),
|
|
619
|
+
steps: [
|
|
620
|
+
{ uses: "actions/setup-node@v7", with: { "node-version": "24" } },
|
|
621
|
+
{
|
|
622
|
+
uses: "actions/download-artifact@v8",
|
|
623
|
+
with: { name: RELEASE_ARTIFACT, path: RELEASE_ARTIFACT },
|
|
624
|
+
},
|
|
625
|
+
{ name: `Publish ${id}`, run: publish },
|
|
626
|
+
],
|
|
627
|
+
};
|
|
628
|
+
priorLegs.push(job);
|
|
629
|
+
}
|
|
588
630
|
jobs.release = {
|
|
589
631
|
// The tag's own release, after every leg: the assets are the same ones the registries got.
|
|
590
632
|
// Skipped legs must not skip it, only failed ones; hence the explicit condition.
|
|
@@ -620,10 +662,7 @@ function releaseDocument(project) {
|
|
|
620
662
|
// A tag the dispatch is cutting does not exist yet: the Release creates it here.
|
|
621
663
|
target_commitish: RELEASE_SHA,
|
|
622
664
|
generate_release_notes: true,
|
|
623
|
-
|
|
624
|
-
files: has(project, "pypi")
|
|
625
|
-
? `${RELEASE_ARTIFACT}/*\n${RELEASE_ARTIFACT}/pypi/*`
|
|
626
|
-
: `${RELEASE_ARTIFACT}/*`,
|
|
665
|
+
files: `${RELEASE_ARTIFACT}/**/*`,
|
|
627
666
|
fail_on_unmatched_files: true,
|
|
628
667
|
},
|
|
629
668
|
},
|
|
@@ -164,10 +164,10 @@ describe("workflows", () => {
|
|
|
164
164
|
{
|
|
165
165
|
name: "Publish package distributions to PyPI",
|
|
166
166
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
167
|
-
with: { "packages-dir": "release-assets/pypi/" },
|
|
167
|
+
with: { "packages-dir": "release-assets/pypi/", "skip-existing": true },
|
|
168
168
|
},
|
|
169
169
|
]);
|
|
170
|
-
expect(release.jobs.release.steps.at(-1)?.with.files).toBe("release-assets
|
|
170
|
+
expect(release.jobs.release.steps.at(-1)?.with.files).toBe("release-assets/**/*");
|
|
171
171
|
expect(packageSteps(python).find((step) => step.name === "web build")).toBeDefined();
|
|
172
172
|
expect(packageSteps(python).find((step) => step.name === "python distributions")?.run).toBe("uv build --out-dir dist/release/pypi");
|
|
173
173
|
const mcpbPython = project(["pypi", "mcp", "mcpb", "web"], {
|
|
@@ -532,6 +532,7 @@ describe("gate", () => {
|
|
|
532
532
|
"toolfactory validate",
|
|
533
533
|
"author checks",
|
|
534
534
|
"author tests",
|
|
535
|
+
"author integration tests",
|
|
535
536
|
"openclaw end-to-end (scripted model, no LLM key)",
|
|
536
537
|
]);
|
|
537
538
|
// `npm ci` and a global CLI install are the runner's job, not a checkout's: `toolfactory gate`
|
package/dist/toolfactory/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ const operations = [...authored, web];
|
|
|
11
11
|
const program = new Command()
|
|
12
12
|
.name("toolfactory")
|
|
13
13
|
.description("Build an agent tool once; ship it as Agent Skills, MCP servers, Agent Plugins / Claude / Codex / Cursor bundles, OpenClaw and Hermes plugins, CLIs, and packages, with the tests and releases to match.")
|
|
14
|
-
.version("0.
|
|
14
|
+
.version("0.2.0");
|
|
15
15
|
for (const op of operations) {
|
|
16
16
|
if (!serves(op, "cli"))
|
|
17
17
|
continue;
|
package/dist/toolfactory/mcp.js
CHANGED
|
@@ -30,7 +30,7 @@ const operations = [...authored, web];
|
|
|
30
30
|
const introspecting = Boolean(process.env.TOOLFACTORY_INTROSPECT);
|
|
31
31
|
/** Builds one server instance with every servable operation registered; called once for stdio, once per HTTP request. */
|
|
32
32
|
function createServer() {
|
|
33
|
-
const server = new McpServer({ name: "toolfactory", version: "0.
|
|
33
|
+
const server = new McpServer({ name: "toolfactory", version: "0.2.0" });
|
|
34
34
|
for (const op of operations) {
|
|
35
35
|
if (!introspecting && !serves(op, "mcp"))
|
|
36
36
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolfactory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Build an agent tool once; ship it as Agent Skills, MCP servers, Agent Plugins / Claude / Codex / Cursor bundles, OpenClaw and Hermes plugins, CLIs, and packages, with the tests and releases to match.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"check": "biome check . && tsc -p tsconfig.json --noEmit",
|
|
18
18
|
"format": "biome format --write .",
|
|
19
19
|
"test": "vitest run --passWithNoTests",
|
|
20
|
+
"test:integration": "node --import tsx scripts/check-vscode.mjs",
|
|
20
21
|
"toolfactory": "node --import tsx src/toolfactory/cli.ts",
|
|
21
22
|
"vendor:template": "node --import tsx src/hosts/template.ts --write"
|
|
22
23
|
},
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { Project } from "../model.js";
|
|
3
|
+
import {
|
|
4
|
+
nativeInstallLines,
|
|
5
|
+
nativePackageConfigSchema,
|
|
6
|
+
nativePackageSteps,
|
|
7
|
+
nativePublishJobs,
|
|
8
|
+
nativeRegistryRows,
|
|
9
|
+
} from "./native.js";
|
|
10
|
+
|
|
11
|
+
function project(
|
|
12
|
+
entries: unknown[],
|
|
13
|
+
surfaces = entries.map((entry) => (entry as { id: string }).id),
|
|
14
|
+
): Project {
|
|
15
|
+
return {
|
|
16
|
+
root: "/repo",
|
|
17
|
+
tool: {
|
|
18
|
+
schemaVersion: 1,
|
|
19
|
+
identity: "package.json",
|
|
20
|
+
binding: "typescript",
|
|
21
|
+
surfaces: surfaces as unknown as Project["tool"]["surfaces"],
|
|
22
|
+
bundle: { runtime: "package" },
|
|
23
|
+
tests: { examples: {} },
|
|
24
|
+
nativePackages: entries,
|
|
25
|
+
} as unknown as Project["tool"],
|
|
26
|
+
identity: { name: "tool", version: "1.2.3", repository: "https://github.com/acme/tool" },
|
|
27
|
+
identityExtra: {},
|
|
28
|
+
operations: [],
|
|
29
|
+
toolfactoryVersion: "0.1.0",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const cargo = {
|
|
33
|
+
id: "cargo",
|
|
34
|
+
path: "native/cli",
|
|
35
|
+
identity: "Cargo.toml",
|
|
36
|
+
name: "acme-cli",
|
|
37
|
+
versionCommand: "cargo pkgid | sed 's/.*@//'",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe("native package configuration", () => {
|
|
41
|
+
it("requires the package path, authored identity, and registry identity", () => {
|
|
42
|
+
expect(() => nativePackageConfigSchema.parse({ id: "cargo", path: "native" })).toThrow();
|
|
43
|
+
expect(nativePackageConfigSchema.parse(cargo)).toEqual(cargo);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("rejects a selected registry with no config or duplicate configuration", () => {
|
|
47
|
+
expect(() => nativePackageSteps(project([], ["cargo"]))).toThrow(/requires exactly one/);
|
|
48
|
+
expect(() => nativePackageSteps(project([cargo, cargo]))).toThrow(/repeats/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("packages authored crates and checks their version against the release", () => {
|
|
52
|
+
const steps = nativePackageSteps(project([cargo]));
|
|
53
|
+
expect(steps.map((step) => step.name)).toEqual([
|
|
54
|
+
"cargo version matches release",
|
|
55
|
+
"Cargo crate",
|
|
56
|
+
]);
|
|
57
|
+
expect(steps[0]?.run).toContain("= '1.2.3'");
|
|
58
|
+
expect(steps[1]?.run).toContain("cargo package --manifest-path 'native/cli/Cargo.toml'");
|
|
59
|
+
expect(steps[1]?.run).toContain("--target-dir 'dist/native-build/cargo'");
|
|
60
|
+
expect(steps[1]?.run).not.toContain("/repo");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("reads structured Cargo metadata and the raw MSBuild version", () => {
|
|
64
|
+
const cargoDefault = {
|
|
65
|
+
id: "cargo",
|
|
66
|
+
path: "native/cli",
|
|
67
|
+
identity: "Cargo.toml",
|
|
68
|
+
name: "acme-cli",
|
|
69
|
+
};
|
|
70
|
+
const nuget = {
|
|
71
|
+
id: "nuget",
|
|
72
|
+
path: "dotnet",
|
|
73
|
+
identity: "Tool.csproj",
|
|
74
|
+
name: "Acme.Tool",
|
|
75
|
+
};
|
|
76
|
+
const steps = nativePackageSteps(project([cargoDefault, nuget]));
|
|
77
|
+
expect(steps[0]?.run).toContain(
|
|
78
|
+
"cargo metadata --no-deps --format-version 1 --manifest-path 'Cargo.toml'",
|
|
79
|
+
);
|
|
80
|
+
expect(steps[0]?.run).toContain("JSON.parse(s).packages.find");
|
|
81
|
+
expect(steps[2]?.run).toContain("dotnet msbuild 'Tool.csproj' -nologo -getProperty:Version");
|
|
82
|
+
expect(steps[2]?.run).not.toContain("sed -n");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("leaves VCS-discovered Packagist and Go modules for the final release tag", () => {
|
|
86
|
+
const entries = [
|
|
87
|
+
{
|
|
88
|
+
id: "packagist",
|
|
89
|
+
path: ".",
|
|
90
|
+
identity: "composer.json",
|
|
91
|
+
name: "acme/tool",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "go-module",
|
|
95
|
+
path: ".",
|
|
96
|
+
identity: "go.mod",
|
|
97
|
+
name: "github.com/acme/tool",
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
expect(nativePackageSteps(project(entries))).toEqual([]);
|
|
101
|
+
expect(nativeInstallLines(project(entries))).toEqual([
|
|
102
|
+
"`composer require acme/tool`",
|
|
103
|
+
"`go get github.com/acme/tool@v1.2.3`",
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("uses direct upstream publishing commands and exposes only their actual credentials", () => {
|
|
108
|
+
const entries = [
|
|
109
|
+
cargo,
|
|
110
|
+
{
|
|
111
|
+
id: "nuget",
|
|
112
|
+
path: "dotnet",
|
|
113
|
+
identity: "Tool.csproj",
|
|
114
|
+
name: "Acme.Tool",
|
|
115
|
+
versionCommand: "dotnet msbuild -getProperty:Version Tool.csproj",
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
const jobs = nativePublishJobs(
|
|
119
|
+
project(entries),
|
|
120
|
+
"release-assets",
|
|
121
|
+
"${{ needs.gate.outputs.sha }}",
|
|
122
|
+
);
|
|
123
|
+
expect(jobs["publish-cargo"]?.steps).toContainEqual(
|
|
124
|
+
expect.objectContaining({ run: expect.stringContaining("cargo publish") }),
|
|
125
|
+
);
|
|
126
|
+
expect(jobs["publish-nuget"]?.steps).toContainEqual(
|
|
127
|
+
expect.objectContaining({ run: expect.stringContaining("dotnet nuget push") }),
|
|
128
|
+
);
|
|
129
|
+
expect(jobs["publish-nuget"]?.steps).toContainEqual(
|
|
130
|
+
expect.objectContaining({
|
|
131
|
+
uses: "actions/setup-dotnet@v5",
|
|
132
|
+
with: { "dotnet-version": "8.0.x" },
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
expect(nativeRegistryRows(project(entries)).map((row) => row.secrets)).toEqual([
|
|
136
|
+
["CARGO_REGISTRY_TOKEN"],
|
|
137
|
+
["NUGET_API_KEY"],
|
|
138
|
+
]);
|
|
139
|
+
expect(jobs["publish-cargo"]?.needs).toEqual(["gate", "package"]);
|
|
140
|
+
expect(jobs["publish-cargo"]?.if).toBe("needs.gate.outputs.cargo == 'true'");
|
|
141
|
+
expect(jobs["publish-cargo"]?.steps).not.toContainEqual(
|
|
142
|
+
expect.objectContaining({ uses: "dtolnay/rust-toolchain@stable" }),
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("builds a gem from its package directory before moving the resulting asset", () => {
|
|
147
|
+
const steps = nativePackageSteps(
|
|
148
|
+
project([
|
|
149
|
+
{
|
|
150
|
+
id: "rubygems",
|
|
151
|
+
path: "native/ruby",
|
|
152
|
+
identity: "acme.gemspec",
|
|
153
|
+
name: "acme",
|
|
154
|
+
versionCommand: "ruby -e 'puts 1.2.3'",
|
|
155
|
+
},
|
|
156
|
+
]),
|
|
157
|
+
);
|
|
158
|
+
expect(steps[1]?.run).toContain("(cd 'native/ruby' && gem build --strict 'acme.gemspec')");
|
|
159
|
+
expect(steps[1]?.run).toContain("mv 'native/ruby/acme-1.2.3.gem'");
|
|
160
|
+
});
|
|
161
|
+
});
|