@yanglaofish/fishtools 0.4.0 → 0.5.1
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 +33 -5
- package/bin/fst.js +1 -1
- package/manual/README.md +33 -5
- package/manual/docs/deployment.md +3 -3
- package/manual/docs/kernel-generation.md +97 -0
- package/manual/docs/modules.md +57 -0
- package/manual/docs/plugin-development.md +120 -10
- package/manual/docs/releases.md +24 -0
- package/manual/docs/research/2026-10-11-plugin-kernel-capabilities.md +6 -6
- package/manual/docs/roadmap.md +14 -3
- package/manual/docs/superpowers/plans/2026-10-09-workflow-mvp.md +5 -5
- package/manual/docs/superpowers/plans/2026-10-11-public-plugin-fixes.md +2 -2
- package/manual/docs/superpowers/plans/2026-10-12-kernel-generation.md +22 -0
- package/manual/docs/superpowers/plans/2026-10-12-modular-kernel.md +40 -0
- package/manual/docs/superpowers/reviews/2026-10-12-kernel-generation-review.md +27 -0
- package/manual/docs/superpowers/reviews/2026-10-12-modular-kernel-review.md +32 -0
- package/manual/docs/superpowers/reviews/2026-10-13-0.5.0-followup-review.md +67 -0
- package/manual/docs/superpowers/reviews/2026-10-13-kernel-runtime-release-review.md +19 -0
- package/manual/docs/superpowers/specs/2026-10-12-action-network-policy-design.md +52 -0
- package/manual/docs/superpowers/specs/2026-10-12-kernel-generation-design.md +38 -0
- package/manual/docs/superpowers/specs/2026-10-12-modular-kernel-design.md +70 -0
- package/manual/docs/superpowers/specs/2026-10-12-plugin-readiness-design.md +21 -0
- package/manual/docs/superpowers/specs/2026-10-12-runtime-maintenance-granularity-design.md +35 -0
- package/manual/docs/superpowers/specs/2026-10-12-workflow-variation-design.md +19 -0
- package/manual/docs/verification.md +55 -2
- package/manual/packages/core/README.md +44 -0
- package/manual/packages/core/package.json +38 -0
- package/manual/packages/core/src/application.js +251 -85
- package/manual/packages/core/src/bundle.js +2 -0
- package/manual/packages/core/src/development-watch-worker.js +69 -0
- package/manual/packages/core/src/development.js +146 -0
- package/manual/packages/core/src/environments.js +2 -0
- package/manual/packages/core/src/export-snapshot.js +42 -0
- package/manual/packages/core/src/plugin-update.js +108 -0
- package/manual/packages/core/src/process.js +2 -0
- package/manual/packages/core/src/resources.js +31 -0
- package/manual/packages/core/src/version.js +2 -0
- package/manual/packages/fst-cli/LICENSE +21 -0
- package/manual/packages/fst-cli/README.md +59 -0
- package/{node_modules/@mytools/core → manual/packages/fst-cli}/bin/fst.js +55 -23
- package/manual/packages/fst-cli/package.json +50 -0
- package/manual/packages/fst-cli/src/development.js +53 -0
- package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/model.js +1 -1
- package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/navigation.js +1 -1
- package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/presentation.js +9 -1
- package/manual/packages/fst-cli/src/terminal/run-feed.js +119 -0
- package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-viewer.js +9 -7
- package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/schema.js +1 -1
- package/manual/packages/{core → fst-cli}/src/terminal/session.js +137 -9
- package/manual/packages/{core → fst-cli}/src/terminal/view.js +20 -3
- package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/workflow-menu.js +17 -2
- package/manual/packages/fst-devtools/LICENSE +21 -0
- package/manual/packages/fst-devtools/README.md +28 -0
- package/manual/packages/fst-devtools/package.json +29 -0
- package/{node_modules/@mytools/core → manual/packages/fst-devtools}/src/documentation.js +3 -1
- package/manual/packages/fst-devtools/src/plugin-quality.js +136 -0
- package/{node_modules/@mytools/core → manual/packages/fst-devtools}/src/plugin-tools.js +24 -5
- package/manual/packages/fst-logs/LICENSE +21 -0
- package/manual/packages/fst-logs/README.md +67 -0
- package/manual/packages/fst-logs/package.json +26 -0
- package/manual/packages/fst-logs/src/log-bounds.js +91 -0
- package/manual/packages/fst-logs/src/run-logs.js +224 -0
- package/manual/packages/fst-logs/src/run-preview.js +18 -0
- package/manual/packages/fst-logs/src/step-summaries.js +28 -0
- package/manual/packages/fst-platform/LICENSE +21 -0
- package/manual/packages/fst-platform/README.md +73 -0
- package/manual/packages/fst-platform/package.json +33 -0
- package/manual/packages/fst-platform/src/bundle.js +17 -0
- package/manual/packages/fst-platform/src/environments.js +67 -0
- package/manual/packages/fst-platform/src/file-probe-engine.js +183 -0
- package/manual/packages/fst-platform/src/file-probe-worker.js +43 -0
- package/manual/packages/fst-platform/src/file-probe.js +6 -0
- package/manual/packages/fst-platform/src/json.js +5 -0
- package/manual/packages/fst-platform/src/network.js +199 -0
- package/manual/packages/fst-platform/src/npm.js +11 -0
- package/manual/packages/fst-platform/src/process.js +110 -0
- package/manual/packages/fst-plugin/LICENSE +21 -0
- package/manual/packages/fst-plugin/README.md +29 -0
- package/manual/packages/fst-plugin/package.json +29 -0
- package/{node_modules/@mytools/core → manual/packages/fst-plugin}/src/contracts.js +30 -3
- package/manual/packages/fst-plugin/src/host.js +235 -0
- package/manual/packages/fst-plugin/src/loader.js +21 -0
- package/manual/packages/fst-plugin/src/privileges.js +91 -0
- package/{node_modules/@mytools/core → manual/packages/fst-plugin}/src/runtime.js +27 -8
- package/manual/packages/fst-workflow/LICENSE +21 -0
- package/manual/packages/fst-workflow/README.md +28 -0
- package/manual/packages/fst-workflow/package.json +24 -0
- package/{node_modules/@mytools/core → manual/packages/fst-workflow}/src/control.js +3 -2
- package/manual/packages/{core → fst-workflow}/src/engine.js +2 -2
- package/manual/packages/fst-workspace/LICENSE +21 -0
- package/manual/packages/fst-workspace/README.md +32 -0
- package/manual/packages/fst-workspace/package.json +34 -0
- package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/bundle-workspace.js +1 -1
- package/manual/packages/fst-workspace/src/index.js +3 -0
- package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/plugins.js +6 -36
- package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/workflows.js +40 -7
- package/manual/packages/pack/LICENSE +21 -0
- package/manual/packages/pack/README.md +31 -0
- package/manual/packages/pack/archive.js +15 -0
- package/manual/packages/pack/dependencies.js +105 -0
- package/manual/packages/pack/environments.js +50 -0
- package/manual/packages/pack/index.js +103 -0
- package/manual/packages/pack/package.json +29 -0
- package/manual/packages/pack/runtime.js +61 -0
- package/manual/packages/sdk/README.md +31 -0
- package/manual/packages/sdk/conditions.js +1 -0
- package/manual/packages/sdk/package.json +23 -1
- package/manual/plugins/web/README.md +29 -0
- package/manual/tests/application.test.js +6 -4
- package/node_modules/@mytools/core/README.md +44 -0
- package/node_modules/@mytools/core/package.json +17 -17
- package/node_modules/@mytools/core/src/application.js +251 -85
- package/node_modules/@mytools/core/src/bundle.js +2 -17
- package/node_modules/@mytools/core/src/development-watch-worker.js +69 -0
- package/node_modules/@mytools/core/src/development.js +146 -0
- package/node_modules/@mytools/core/src/environments.js +2 -67
- package/node_modules/@mytools/core/src/export-snapshot.js +42 -0
- package/node_modules/@mytools/core/src/plugin-update.js +108 -0
- package/node_modules/@mytools/core/src/process.js +2 -80
- package/node_modules/@mytools/core/src/version.js +1 -1
- package/node_modules/@mytools/fst-cli/LICENSE +21 -0
- package/node_modules/@mytools/fst-cli/README.md +59 -0
- package/node_modules/@mytools/fst-cli/bin/fst.js +124 -0
- package/node_modules/@mytools/fst-cli/package.json +50 -0
- package/node_modules/@mytools/fst-cli/src/cli-run.js +12 -0
- package/node_modules/@mytools/fst-cli/src/development.js +53 -0
- package/node_modules/@mytools/fst-cli/src/terminal/conditions.js +32 -0
- package/node_modules/@mytools/fst-cli/src/terminal/log-viewer.js +45 -0
- package/node_modules/@mytools/fst-cli/src/terminal/model.js +104 -0
- package/node_modules/@mytools/fst-cli/src/terminal/navigation.js +17 -0
- package/node_modules/@mytools/fst-cli/src/terminal/presentation.js +50 -0
- package/node_modules/@mytools/fst-cli/src/terminal/run-feed.js +119 -0
- package/node_modules/@mytools/fst-cli/src/terminal/run-presentation.js +60 -0
- package/node_modules/@mytools/fst-cli/src/terminal/run-viewer.js +106 -0
- package/node_modules/@mytools/fst-cli/src/terminal/schema.js +52 -0
- package/node_modules/@mytools/{core → fst-cli}/src/terminal/session.js +137 -9
- package/node_modules/@mytools/fst-cli/src/terminal/theme.js +29 -0
- package/node_modules/@mytools/{core → fst-cli}/src/terminal/view.js +20 -3
- package/node_modules/@mytools/fst-cli/src/terminal/workflow-menu.js +65 -0
- package/node_modules/@mytools/fst-devtools/LICENSE +21 -0
- package/node_modules/@mytools/fst-devtools/README.md +28 -0
- package/node_modules/@mytools/fst-devtools/package.json +29 -0
- package/node_modules/@mytools/fst-devtools/src/documentation.js +67 -0
- package/node_modules/@mytools/fst-devtools/src/plugin-quality.js +136 -0
- package/node_modules/@mytools/fst-devtools/src/plugin-tools.js +152 -0
- package/node_modules/@mytools/fst-logs/LICENSE +21 -0
- package/node_modules/@mytools/fst-logs/README.md +67 -0
- package/node_modules/@mytools/fst-logs/package.json +26 -0
- package/node_modules/@mytools/fst-logs/src/log-bounds.js +91 -0
- package/node_modules/@mytools/fst-logs/src/markdown-log.js +55 -0
- package/node_modules/@mytools/fst-logs/src/run-logs.js +224 -0
- package/node_modules/@mytools/fst-logs/src/run-preview.js +18 -0
- package/node_modules/@mytools/fst-logs/src/step-summaries.js +28 -0
- package/node_modules/@mytools/fst-platform/LICENSE +21 -0
- package/node_modules/@mytools/fst-platform/README.md +73 -0
- package/node_modules/@mytools/fst-platform/package.json +33 -0
- package/node_modules/@mytools/fst-platform/src/bundle.js +17 -0
- package/node_modules/@mytools/fst-platform/src/environments.js +67 -0
- package/node_modules/@mytools/fst-platform/src/file-probe-engine.js +183 -0
- package/node_modules/@mytools/fst-platform/src/file-probe-worker.js +43 -0
- package/node_modules/@mytools/fst-platform/src/file-probe.js +6 -0
- package/node_modules/@mytools/fst-platform/src/json.js +5 -0
- package/node_modules/@mytools/fst-platform/src/network.js +199 -0
- package/node_modules/@mytools/fst-platform/src/npm.js +11 -0
- package/node_modules/@mytools/fst-platform/src/process.js +110 -0
- package/node_modules/@mytools/fst-plugin/LICENSE +21 -0
- package/node_modules/@mytools/fst-plugin/README.md +29 -0
- package/node_modules/@mytools/fst-plugin/package.json +29 -0
- package/node_modules/@mytools/fst-plugin/src/contracts.js +86 -0
- package/node_modules/@mytools/fst-plugin/src/host.js +235 -0
- package/node_modules/@mytools/fst-plugin/src/loader.js +21 -0
- package/node_modules/@mytools/fst-plugin/src/privileges.js +91 -0
- package/{manual/packages/core → node_modules/@mytools/fst-plugin}/src/runtime.js +27 -8
- package/node_modules/@mytools/fst-workflow/LICENSE +21 -0
- package/node_modules/@mytools/fst-workflow/README.md +28 -0
- package/node_modules/@mytools/fst-workflow/package.json +24 -0
- package/node_modules/@mytools/fst-workflow/src/control.js +64 -0
- package/node_modules/@mytools/{core → fst-workflow}/src/engine.js +2 -2
- package/node_modules/@mytools/fst-workspace/LICENSE +21 -0
- package/node_modules/@mytools/fst-workspace/README.md +32 -0
- package/node_modules/@mytools/fst-workspace/package.json +34 -0
- package/node_modules/@mytools/fst-workspace/src/bundle-workspace.js +39 -0
- package/node_modules/@mytools/fst-workspace/src/index.js +3 -0
- package/node_modules/@mytools/fst-workspace/src/plugin-installations.js +84 -0
- package/node_modules/@mytools/fst-workspace/src/plugins.js +35 -0
- package/node_modules/@mytools/fst-workspace/src/project.js +29 -0
- package/node_modules/@mytools/fst-workspace/src/workflows.js +150 -0
- package/node_modules/@mytools/fst-workspace/templates/control-demo.yaml +47 -0
- package/node_modules/@mytools/fst-workspace/templates/hello.yaml +35 -0
- package/node_modules/@mytools/fst-workspace/templates/python-check.yaml +26 -0
- package/node_modules/@mytools/fst-workspace/templates/system-check.yaml +11 -0
- package/node_modules/@mytools/pack/README.md +31 -0
- package/node_modules/@mytools/pack/dependencies.js +7 -2
- package/node_modules/@mytools/pack/environments.js +1 -1
- package/node_modules/@mytools/pack/index.js +33 -25
- package/node_modules/@mytools/pack/package.json +29 -1
- package/node_modules/@mytools/sdk/README.md +31 -0
- package/node_modules/@mytools/sdk/conditions.js +1 -0
- package/node_modules/@mytools/sdk/diagnostics.js +27 -0
- package/node_modules/@mytools/sdk/package.json +23 -1
- package/node_modules/@mytools/sdk/schema-path.js +31 -0
- package/node_modules/@mytools/sdk/validation.js +78 -0
- package/node_modules/@mytools/web/README.md +29 -0
- package/node_modules/@mytools/web/package.json +7 -0
- package/node_modules/@mytools/web/public/THIRD_PARTY_NOTICES.txt +1440 -1240
- package/node_modules/@mytools/web/public/assets/index-BaVzHycP.js +70 -0
- package/node_modules/@mytools/web/public/assets/index-DA8Ae92t.css +1 -0
- package/node_modules/@mytools/web/public/index.html +2 -2
- package/node_modules/@mytools/web/server.js +24 -17
- package/node_modules/@mytools/web/src/model.js +84 -0
- package/node_modules/acorn/CHANGELOG.md +1012 -0
- package/node_modules/acorn/LICENSE +21 -0
- package/node_modules/acorn/README.md +309 -0
- package/node_modules/acorn/bin/acorn +4 -0
- package/node_modules/acorn/dist/acorn.d.mts +897 -0
- package/node_modules/acorn/dist/acorn.d.ts +897 -0
- package/node_modules/acorn/dist/acorn.js +6370 -0
- package/node_modules/acorn/dist/acorn.mjs +6341 -0
- package/node_modules/acorn/dist/bin.js +90 -0
- package/node_modules/acorn/package.json +50 -0
- package/node_modules/{minipass-flush/node_modules/minipass → tar}/node_modules/yallist/README.md +8 -7
- package/node_modules/tar/node_modules/yallist/package.json +68 -0
- package/node_modules/yallist/README.md +7 -8
- package/node_modules/yallist/package.json +12 -51
- package/package.json +131 -115
- package/manual/packages/core/src/host.js +0 -139
- package/manual/packages/core/src/terminal/run-feed.js +0 -78
- package/node_modules/@mytools/core/src/host.js +0 -139
- package/node_modules/@mytools/core/src/run-logs.js +0 -63
- package/node_modules/@mytools/core/src/terminal/run-feed.js +0 -78
- package/node_modules/@mytools/web/public/assets/index-DT2B4tMV.js +0 -70
- package/node_modules/@mytools/web/public/assets/index-vuZvrUTT.css +0 -1
- package/node_modules/minipass-flush/node_modules/minipass/node_modules/yallist/package.json +0 -29
- /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/cli-run.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/conditions.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/log-viewer.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/run-presentation.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-cli}/src/terminal/theme.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-logs}/src/markdown-log.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/plugin-installations.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/project.js +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/control-demo.yaml +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/hello.yaml +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/python-check.yaml +0 -0
- /package/{node_modules/@mytools/core → manual/packages/fst-workspace}/templates/system-check.yaml +0 -0
- /package/{node_modules/@mytools/core/src → manual/packages/sdk}/diagnostics.js +0 -0
- /package/{node_modules/@mytools/core/src → manual/packages/sdk}/schema-path.js +0 -0
- /package/{node_modules/@mytools/core/src → manual/packages/sdk}/validation.js +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/LICENSE.md +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.js +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/index.js.map +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/commonjs/package.json +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.d.ts +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.js +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/index.js.map +0 -0
- /package/node_modules/{yallist → tar/node_modules/yallist}/dist/esm/package.json +0 -0
- /package/node_modules/{minipass-flush/node_modules/minipass/node_modules/yallist → yallist}/LICENSE +0 -0
- /package/node_modules/{minipass-flush/node_modules/minipass/node_modules/yallist → yallist}/iterator.js +0 -0
- /package/node_modules/{minipass-flush/node_modules/minipass/node_modules/yallist → yallist}/yallist.js +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mytools/fst-workspace",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./workflows": "./src/workflows.js",
|
|
10
|
+
"./project": "./src/project.js",
|
|
11
|
+
"./plugins": "./src/plugins.js",
|
|
12
|
+
"./installations": "./src/plugin-installations.js",
|
|
13
|
+
"./bundles": "./src/bundle-workspace.js",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"templates",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=24.15.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@mytools/sdk": "0.1.0",
|
|
28
|
+
"@mytools/fst-platform": "0.1.0",
|
|
29
|
+
"@mytools/fst-plugin": "0.1.0",
|
|
30
|
+
"@mytools/fst-workflow": "0.1.0",
|
|
31
|
+
"tar": "7.5.22",
|
|
32
|
+
"yaml": "2.9.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/{node_modules/@mytools/core → manual/packages/fst-workspace}/src/bundle-workspace.js
RENAMED
|
@@ -5,7 +5,7 @@ import {constants} from 'node:fs';
|
|
|
5
5
|
import {randomUUID} from 'node:crypto';
|
|
6
6
|
import {setTimeout} from 'node:timers/promises';
|
|
7
7
|
import {FlowError} from '@mytools/sdk';
|
|
8
|
-
import {readJson} from '
|
|
8
|
+
import {readJson} from '@mytools/fst-platform/json';
|
|
9
9
|
|
|
10
10
|
export async function prepareBundleWorkspace(bundleRoot,requested){
|
|
11
11
|
bundleRoot=path.resolve(bundleRoot);
|
|
@@ -1,40 +1,10 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
|
-
import { readFile, writeFile, mkdir, access } from 'node:fs/promises';
|
|
4
1
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
catch(e) { if(e.code==='ENOENT' && fallback!==undefined) return fallback; throw e; }
|
|
12
|
-
}
|
|
13
|
-
export async function loadPlugin(spec,base) {
|
|
14
|
-
const req=createRequire(path.join(base,'package.json'));
|
|
15
|
-
let entry;
|
|
16
|
-
if(path.isAbsolute(spec) || spec.startsWith('.')) {
|
|
17
|
-
const root=path.resolve(base,spec), pkg=await readJson(path.join(root,'package.json'));
|
|
18
|
-
entry=path.resolve(root,pkg.main??'index.js');
|
|
19
|
-
} else {
|
|
20
|
-
try {entry=req.resolve(spec);} catch {entry=createRequire(import.meta.url).resolve(spec);}
|
|
21
|
-
}
|
|
22
|
-
let root=path.dirname(entry);
|
|
23
|
-
while(root!==path.dirname(root)) {
|
|
24
|
-
try {await access(path.join(root,'package.json'));break;} catch {root=path.dirname(root);}
|
|
25
|
-
}
|
|
26
|
-
const module=await import(pathToFileURL(entry).href);
|
|
27
|
-
return {plugin:module.default,root};
|
|
28
|
-
}
|
|
29
|
-
export async function npm(args,options={}) {
|
|
30
|
-
const candidates=[path.join(path.dirname(process.execPath),'node_modules/npm/bin/npm-cli.js'),process.env.npm_execpath].filter(Boolean);
|
|
31
|
-
for(const cli of candidates) {
|
|
32
|
-
try {await access(cli);} catch {continue;}
|
|
33
|
-
return runProcess(process.execPath,[cli,...args],{timeout:180000,...options});
|
|
34
|
-
}
|
|
35
|
-
return runProcess('npm',args,{timeout:180000,...options});
|
|
36
|
-
}
|
|
37
|
-
|
|
2
|
+
import {mkdir,writeFile,access} from 'node:fs/promises';
|
|
3
|
+
import {randomUUID} from 'node:crypto';
|
|
4
|
+
import {FlowError} from '@mytools/sdk';
|
|
5
|
+
import {readJson} from '@mytools/fst-platform/json';
|
|
6
|
+
import {npm} from '@mytools/fst-platform/npm';
|
|
7
|
+
import {loadPlugin} from '@mytools/fst-plugin/loader';
|
|
38
8
|
export async function installGeneration(projectDir,{add,remove,link,state}) {
|
|
39
9
|
const root=path.join(projectDir,'.flow','installations',randomUUID());
|
|
40
10
|
await mkdir(root,{recursive:true});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { mkdir, readFile, readdir, realpath, writeFile, rename } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, readdir, realpath, writeFile, rename, open, unlink } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
5
|
import { FlowError } from '@mytools/sdk';
|
|
6
|
-
import { assertId, assertSchema, references, referenceParts, emptyObject } from '
|
|
7
|
-
import {resolveSchemaPath} from '
|
|
8
|
-
import {validateCondition,validateStepList} from '
|
|
6
|
+
import { assertId, assertSchema, references, referenceParts, emptyObject } from '@mytools/sdk/schema';
|
|
7
|
+
import {resolveSchemaPath} from '@mytools/sdk/schema-path';
|
|
8
|
+
import {validateCondition,validateStepList} from '@mytools/fst-workflow/control';
|
|
9
9
|
|
|
10
10
|
export class WorkflowStore {
|
|
11
|
-
constructor(root) { this.root = path.resolve(root); }
|
|
11
|
+
constructor(root,{openFile=open}={}) { this.root = path.resolve(root);this.openFile=openFile; }
|
|
12
12
|
file(id) { assertId(id); return path.join(this.root,`${id}.yaml`); }
|
|
13
13
|
async get(id) {
|
|
14
14
|
const file=this.file(id);
|
|
@@ -29,10 +29,43 @@ export class WorkflowStore {
|
|
|
29
29
|
const w=this.validateShape(definition);
|
|
30
30
|
await mkdir(this.root,{recursive:true});
|
|
31
31
|
const file=this.file(w.id), temporary=`${file}.${randomUUID()}.tmp`;
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
let previous;
|
|
33
|
+
try{
|
|
34
|
+
const physical=await realpath(file),root=await realpath(this.root);
|
|
35
|
+
if(!physical.startsWith(root+path.sep))throw new FlowError('工作流越界,不能覆盖工作流目录之外的文件',{code:'VALIDATION'});
|
|
36
|
+
previous=await readFile(file,'utf8');
|
|
37
|
+
}catch(error){if(error.code!=='ENOENT')throw error;}
|
|
38
|
+
if(previous!==undefined){
|
|
39
|
+
const directory=path.join(path.dirname(this.root),'.flow','workflow-backups');
|
|
40
|
+
await mkdir(directory,{recursive:true,mode:0o700});
|
|
41
|
+
await writeFile(path.join(directory,`${w.id}-${randomUUID()}.yaml`),previous,{flag:'wx',mode:0o600});
|
|
42
|
+
}
|
|
43
|
+
try{await writeFile(temporary,YAML.stringify(w),'utf8');await rename(temporary,file);}
|
|
44
|
+
catch(error){try{await unlink(temporary);}catch(cleanup){if(cleanup.code!=='ENOENT')throw new AggregateError([error,cleanup],`工作流保存失败且临时文件无法清理:${temporary}`);}throw error;}
|
|
34
45
|
return w;
|
|
35
46
|
}
|
|
47
|
+
async duplicate(definition,{id,title}={}) {
|
|
48
|
+
const source=this.validateShape(definition);
|
|
49
|
+
const copy=this.validateShape({...source,id:id??`flow-${randomUUID()}`,title:title??`${source.title??source.id} 副本`});
|
|
50
|
+
const file=this.file(copy.id),content=YAML.stringify(copy);
|
|
51
|
+
if(typeof copy.title!=='string'||!copy.title.trim())throw new FlowError('工作流名称不能为空',{code:'VALIDATION'});
|
|
52
|
+
await mkdir(this.root,{recursive:true});
|
|
53
|
+
let handle;
|
|
54
|
+
try{
|
|
55
|
+
// 排他创建保证并发请求也不能覆盖同名工作流。
|
|
56
|
+
handle=await this.openFile(file,'wx');
|
|
57
|
+
await handle.writeFile(content,'utf8');
|
|
58
|
+
await handle.close();handle=null;
|
|
59
|
+
return {definition:copy,path:file};
|
|
60
|
+
}catch(error){
|
|
61
|
+
if(handle){
|
|
62
|
+
try{await handle.close();}catch{}
|
|
63
|
+
try{await unlink(file);}catch(cleanupError){if(cleanupError.code!=='ENOENT')throw new AggregateError([error,cleanupError],`复制失败且无法清理新文件:${file}`);}
|
|
64
|
+
}
|
|
65
|
+
if(error.code==='EEXIST')throw new FlowError(`工作流已存在:${copy.id}`,{code:'VALIDATION',cause:error});
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
36
69
|
async list() {
|
|
37
70
|
await mkdir(this.root,{recursive:true});
|
|
38
71
|
const files=await readdir(this.root);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mytools contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 分发模块
|
|
2
|
+
|
|
3
|
+
包名:`@mytools/pack`。内部 workspace 包,随 `@yanglaofish/fishtools` 统一交付。
|
|
4
|
+
|
|
5
|
+
## 职责与边界
|
|
6
|
+
|
|
7
|
+
根据声明快照规划环境、收集生产依赖、复制运行时、生成清单和归档。不读取活动宿主或工作流存储;CLI 根目录作为发行载荷路径传入。
|
|
8
|
+
|
|
9
|
+
内部依赖:`@mytools/sdk`、`@mytools/fst-platform`、`@mytools/fst-devtools`。
|
|
10
|
+
## 公开入口
|
|
11
|
+
|
|
12
|
+
| 导入路径 | 实现位置 |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `@mytools/pack` | `./index.js` |
|
|
15
|
+
| `@mytools/pack/dependencies` | `./dependencies.js` |
|
|
16
|
+
| `@mytools/pack/runtime` | `./runtime.js` |
|
|
17
|
+
| `@mytools/pack/environments` | `./environments.js` |
|
|
18
|
+
| `@mytools/pack/archive` | `./archive.js` |
|
|
19
|
+
| `@mytools/pack/package.json` | `./package.json` |
|
|
20
|
+
|
|
21
|
+
仅依赖 exports 声明的入口,禁止消费者跨包读取 src/bin 私有文件。入口路径由本模块维护,修改协议时同步消费方回归。
|
|
22
|
+
|
|
23
|
+
## 维护与验证
|
|
24
|
+
|
|
25
|
+
本模块负责上述职责的实现、文档与回归;跨模块接口调整需要同时检查生产者与消费者。具体维护人员尚未指定。
|
|
26
|
+
|
|
27
|
+
在仓库根目录执行:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
node --test tests/modules.test.js tests/pack.test.js tests/dependencies.test.js tests/npm-dist.test.js
|
|
31
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {createWriteStream} from 'node:fs';
|
|
3
|
+
import {ZipArchive,TarArchive} from 'archiver';
|
|
4
|
+
|
|
5
|
+
export async function archiveDirectory(directory,target,executablePaths=new Set()){
|
|
6
|
+
const name=path.basename(directory),archive=`${directory}.${target==='win32-x64'?'zip':'tar.gz'}`;
|
|
7
|
+
const stream=createWriteStream(archive),zip=target==='win32-x64'?new ZipArchive({zlib:{level:6}}):new TarArchive({gzip:true,gzipOptions:{level:6}});
|
|
8
|
+
await new Promise((resolve,reject)=>{
|
|
9
|
+
stream.on('close',resolve);stream.on('error',reject);zip.on('error',reject);zip.pipe(stream);
|
|
10
|
+
// Archiver 的回调给出源相对路径,顶层归档目录由库随后添加。
|
|
11
|
+
zip.directory(directory,name,data=>/\.sh$/.test(data.name)||executablePaths.has(data.name)?{...data,mode:0o755}:data);
|
|
12
|
+
zip.finalize().catch(reject);
|
|
13
|
+
});
|
|
14
|
+
return archive;
|
|
15
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Arborist from '@npmcli/arborist';
|
|
2
|
+
import packlist from 'npm-packlist';
|
|
3
|
+
import { access, copyFile, mkdir, writeFile, realpath,stat,chmod } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { FlowError } from '@mytools/sdk';
|
|
6
|
+
|
|
7
|
+
export async function collectPackages(roots,{omitOptionalPeers=[]}={}) {
|
|
8
|
+
const trees=new Map(), nodes=new Map();
|
|
9
|
+
async function locate(root){
|
|
10
|
+
root=await realpath(root);let base=root,installedRoot;
|
|
11
|
+
for(let current=root;current!==path.dirname(current);current=path.dirname(current)){
|
|
12
|
+
if(path.basename(current)==='node_modules'){
|
|
13
|
+
const owner=path.dirname(current);
|
|
14
|
+
try{await access(path.join(owner,'package.json'));installedRoot=owner;}catch{}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// 使用拥有这条 node_modules 链的最外层包,既保留提升依赖,也避开无关用户目录的锁文件。
|
|
18
|
+
if(installedRoot)base=installedRoot;
|
|
19
|
+
else{
|
|
20
|
+
while(base!==path.dirname(base)){
|
|
21
|
+
try{await access(path.join(base,'package-lock.json'));break;}catch{base=path.dirname(base);}
|
|
22
|
+
}
|
|
23
|
+
if(base===path.dirname(base))base=root;
|
|
24
|
+
}
|
|
25
|
+
if(!trees.has(base))trees.set(base,await new Arborist({path:base}).loadActual());
|
|
26
|
+
const tree=trees.get(base);
|
|
27
|
+
let found=[...tree.inventory.values()].find(n=>path.relative(n.realpath,root)==='' && !n.isLink);
|
|
28
|
+
// 开发引用可能嵌套在无关项目下,其上层 lock 不代表当前插件属于该依赖树。
|
|
29
|
+
if(!found&&base!==root){
|
|
30
|
+
if(!trees.has(root))trees.set(root,await new Arborist({path:root}).loadActual());
|
|
31
|
+
found=[...trees.get(root).inventory.values()].find(n=>path.relative(n.realpath,root)===''&&!n.isLink);
|
|
32
|
+
}
|
|
33
|
+
if(!found)throw new FlowError(`无法解析包的生产依赖:${root}`);
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
function visit(raw){
|
|
37
|
+
const node=raw.isLink?raw.target:raw;
|
|
38
|
+
if(nodes.has(node.realpath))return;
|
|
39
|
+
nodes.set(node.realpath,node);
|
|
40
|
+
for(const edge of node.edgesOut.values()){
|
|
41
|
+
if(edge.type==='dev')continue;
|
|
42
|
+
if(edge.type==='peerOptional'&&omitOptionalPeers.includes(edge.name))continue;
|
|
43
|
+
if(!edge.to){if(edge.optional)continue;throw new FlowError(`${node.name} 缺少依赖 ${edge.name}`);}
|
|
44
|
+
if(!edge.valid)throw new FlowError(`${node.name} 的依赖不兼容:${edge.name}`);
|
|
45
|
+
visit(edge.to);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for(const root of roots)visit(await locate(root));
|
|
49
|
+
return [...nodes.values()];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function copyPackages(nodes,destination,target,{filters=new Map()}={}){
|
|
53
|
+
const included=new Set(nodes.map(n=>n.realpath));
|
|
54
|
+
const top=new Map();
|
|
55
|
+
for(const n of nodes)if(!top.has(n.package.name))top.set(n.package.name,n);
|
|
56
|
+
for(const n of nodes)for(const edge of n.edgesOut.values())if(edge.type!=='dev'&&edge.to&&!top.has(edge.name)){
|
|
57
|
+
const child=edge.to.isLink?edge.to.target:edge.to;if(included.has(child.realpath))top.set(edge.name,child);
|
|
58
|
+
}
|
|
59
|
+
const copied=new Set(),summary=[];
|
|
60
|
+
const placements=new Map([...top].map(([name,node])=>[path.join(destination,'node_modules',name),node]));
|
|
61
|
+
function resolvedAt(from,name){
|
|
62
|
+
let current=from;
|
|
63
|
+
while(current.startsWith(destination+path.sep)||current===destination){
|
|
64
|
+
const placed=placements.get(path.join(current,'node_modules',name));
|
|
65
|
+
if(placed)return placed;
|
|
66
|
+
if(current===destination)break;
|
|
67
|
+
current=path.dirname(current);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function copy(node,dest){
|
|
71
|
+
if(copied.has(dest))return;
|
|
72
|
+
copied.add(dest);await mkdir(dest,{recursive:true});
|
|
73
|
+
// packlist 对项目根只应用发布规则;依赖节点会额外收集依赖,所以单独读取包根。
|
|
74
|
+
const packageRoot=await realpath(node.realpath);
|
|
75
|
+
const filter=filters.get(packageRoot);
|
|
76
|
+
const files=[...new Set([...await packlist(await new Arborist({path:packageRoot}).loadActual()),...(filter?.extraFiles??[])])];
|
|
77
|
+
for(const file of files){
|
|
78
|
+
// 项目可以与开发插件共用根目录;宿主私有状态和安装批次永远不进入插件快照。
|
|
79
|
+
if(file.split(/[\\/]/)[0]==='.flow')continue;
|
|
80
|
+
if(filter&&!filter.include(file))continue;
|
|
81
|
+
const source=path.resolve(packageRoot,file),physical=await realpath(source);
|
|
82
|
+
// 两端先解析实际路径,再按系统规则比较,兼容 Windows 大小写、短路径和目录联接。
|
|
83
|
+
const relative=path.relative(packageRoot,physical);
|
|
84
|
+
if(!relative||relative==='..'||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative))throw new FlowError(`包包含外部资源链接:${node.name}/${file}`);
|
|
85
|
+
const output=path.join(dest,file);await mkdir(path.dirname(output),{recursive:true});await copyFile(source,output);
|
|
86
|
+
await chmod(output,(await stat(source)).mode);
|
|
87
|
+
}
|
|
88
|
+
summary.push({name:node.package.name,version:node.version,path:path.relative(destination,dest).split(path.sep).join('/'),license:node.package.license??'UNSPECIFIED'});
|
|
89
|
+
const nested=[];
|
|
90
|
+
// 先预留全部直接依赖的位置,再递归;兄弟依赖也会遮蔽其子包的查找路径。
|
|
91
|
+
for(const edge of node.edgesOut.values()){
|
|
92
|
+
if(edge.type==='dev'||!edge.to)continue;
|
|
93
|
+
const child=edge.to.isLink?edge.to.target:edge.to;
|
|
94
|
+
if(!included.has(child.realpath))continue;
|
|
95
|
+
if(resolvedAt(dest,edge.name)?.realpath===child.realpath)continue;
|
|
96
|
+
const childDest=path.join(dest,'node_modules',edge.name);
|
|
97
|
+
placements.set(childDest,child);nested.push([child,childDest]);
|
|
98
|
+
}
|
|
99
|
+
for(const [child,childDest] of nested)await copy(child,childDest);
|
|
100
|
+
}
|
|
101
|
+
for(const [name,node] of top)await copy(node,path.join(destination,'node_modules',name));
|
|
102
|
+
// 明确应用层安装根,让便携包在离线再次导出时可重建生产依赖树。
|
|
103
|
+
await writeFile(path.join(destination,'package.json'),JSON.stringify({name:'fishtools-bundle-app',private:true,dependencies:Object.fromEntries([...top].map(([name,node])=>[name,node.version]))},null,2));
|
|
104
|
+
return summary;
|
|
105
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {realpath,readdir,stat} from 'node:fs/promises';
|
|
3
|
+
import {inside,resolveEnvironments,environmentDeclarations} from '@mytools/fst-platform/environments';
|
|
4
|
+
import {FlowError} from '@mytools/sdk';
|
|
5
|
+
|
|
6
|
+
// 只核对被声明的资源能否完整收集;版本和业务兼容性由作者负责。
|
|
7
|
+
export async function planEnvironments(plugins,target,overrides={}){
|
|
8
|
+
const entries=[],filters=new Map(),warnings=[],allFiles=new Map();
|
|
9
|
+
const platform=target.split('-')[0],architecture=target.split('-')[1];
|
|
10
|
+
for(const plugin of plugins){
|
|
11
|
+
if(plugin.platforms&&!plugin.platforms.includes(platform)||plugin.architectures&&!plugin.architectures.includes(architecture))warnings.push(`${plugin.id} 的作者声明未包含 ${target},仍按所选目标打包`);
|
|
12
|
+
const root=await realpath(plugin.root),excluded=new Set(),selected=new Set(),extraFiles=new Set();
|
|
13
|
+
for(const [actionName,action] of [['',undefined],...Object.entries(plugin.actions??{})]){
|
|
14
|
+
const declarations=environmentDeclarations(plugin,action);
|
|
15
|
+
const resolved=resolveEnvironments({...plugin,root},action,overrides[plugin.id],target);
|
|
16
|
+
for(const [id,declaration] of Object.entries(declarations)){
|
|
17
|
+
const e=resolved[id];
|
|
18
|
+
for(const artifact of Object.values(declaration.artifacts??{})){
|
|
19
|
+
const directory=inside(root,artifact.directory);if(directory===root)throw new FlowError('环境资源目录不能是插件根目录');
|
|
20
|
+
excluded.add(directory);
|
|
21
|
+
}
|
|
22
|
+
let bytes=0;
|
|
23
|
+
if(e.source==='bundled'){
|
|
24
|
+
selected.add(e.root);
|
|
25
|
+
const seen=new Set();
|
|
26
|
+
const scan=async dir=>{
|
|
27
|
+
const physical=await realpath(dir),relative=path.relative(root,physical);
|
|
28
|
+
if(relative==='..'||relative.startsWith('..'+path.sep)||path.isAbsolute(relative))throw new FlowError(`环境资源越界:${dir}`);
|
|
29
|
+
const info=await stat(dir);
|
|
30
|
+
if(info.isDirectory()){
|
|
31
|
+
if(seen.has(physical))throw new FlowError(`环境目录循环:${dir}`);seen.add(physical);
|
|
32
|
+
for(const name of await readdir(dir))await scan(path.join(dir,name));
|
|
33
|
+
seen.delete(physical);
|
|
34
|
+
}
|
|
35
|
+
else if(info.isFile()){bytes+=info.size;extraFiles.add(path.relative(root,dir));allFiles.set(dir,info.size);}
|
|
36
|
+
else throw new FlowError(`环境资源不是普通文件:${dir}`);
|
|
37
|
+
};
|
|
38
|
+
await scan(e.root);
|
|
39
|
+
if(!(await stat(e.executable)).isFile())throw new FlowError(`环境入口不是文件:${e.executable}`);
|
|
40
|
+
}
|
|
41
|
+
const key=`${plugin.id}/${actionName||'*'}/${id}`;
|
|
42
|
+
entries.push({key,plugin:plugin.id,action:actionName||undefined,id,source:e.source,version:e.version,packages:e.packages,allowedSources:declaration.allowedSources??[declaration.source],bytes,executable:e.source==='bundled'?path.relative(root,e.executable).split(path.sep).join('/'):e.executable});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const within=(file,dir)=>file===dir||file.startsWith(dir+path.sep);
|
|
46
|
+
filters.set(root,{extraFiles:[...extraFiles],include:file=>{const full=path.resolve(root,file);return [...selected].some(dir=>within(full,dir))||![...excluded].some(dir=>within(full,dir));}});
|
|
47
|
+
}
|
|
48
|
+
for(const [plugin,values] of Object.entries(overrides))for(const id of Object.keys(values))if(!entries.some(e=>e.plugin===plugin&&e.id===id))throw new FlowError(`环境覆盖未匹配声明:${plugin}/${id}`);
|
|
49
|
+
return {entries,filters,warnings,bytes:[...allFiles.values()].reduce((sum,bytes)=>sum+bytes,0)};
|
|
50
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { mkdir, writeFile, readFile, readdir, chmod,cp } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { collectPackages, copyPackages } from './dependencies.js';
|
|
5
|
+
import { copyRuntime, digest } from './runtime.js';
|
|
6
|
+
import { FlowError } from '@mytools/sdk';
|
|
7
|
+
import YAML from 'yaml';
|
|
8
|
+
import { verifyBundle } from '@mytools/fst-platform/bundle';
|
|
9
|
+
import {planEnvironments} from './environments.js';
|
|
10
|
+
import {archiveDirectory} from './archive.js';
|
|
11
|
+
import {copyDocumentation} from '@mytools/fst-devtools/documentation';
|
|
12
|
+
export {verifyBundle};
|
|
13
|
+
|
|
14
|
+
async function hashes(root,dir=root,result={}){
|
|
15
|
+
for(const entry of await readdir(dir,{withFileTypes:true})){
|
|
16
|
+
const file=path.join(dir,entry.name);
|
|
17
|
+
if(entry.isDirectory())await hashes(root,file,result);
|
|
18
|
+
else if(entry.isFile())result[path.relative(root,file).split(path.sep).join('/')]=digest(await readFile(file));
|
|
19
|
+
else throw new FlowError(`分发目录包含未处理链接:${file}`);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
function declaredPrivileges(closure,plugins){
|
|
24
|
+
const used=new Set();
|
|
25
|
+
const scan=steps=>{for(const step of steps??[]){
|
|
26
|
+
if(step.action)used.add(step.action);
|
|
27
|
+
if(step.branch){scan(step.branch.then);scan(step.branch.else);}
|
|
28
|
+
if(step.parallel)scan(step.parallel.steps);
|
|
29
|
+
}};
|
|
30
|
+
for(const workflow of closure.workflows)scan(workflow.steps);
|
|
31
|
+
const report=[];
|
|
32
|
+
for(const plugin of plugins){
|
|
33
|
+
if(plugin.requirements?.privilege)report.push({plugin:plugin.id,scope:'plugin',privilege:plugin.requirements.privilege});
|
|
34
|
+
for(const [name,action] of Object.entries(plugin.actions??{}))if(used.has(`${plugin.id}:${name}`)&&action.requirements?.privilege)report.push({plugin:plugin.id,action:name,scope:'action',privilege:action.requirements.privilege});
|
|
35
|
+
}
|
|
36
|
+
return report;
|
|
37
|
+
}
|
|
38
|
+
async function prepare(source,{entries,target,includeWeb=false,portable=false,environments={}}){
|
|
39
|
+
// 兼容既有 packApplication(app) 调用,但只使用明确的快照 Interface。
|
|
40
|
+
const snapshot=typeof source.exportSnapshot==='function'?await source.exportSnapshot({entries,includeWeb,portable}):source;
|
|
41
|
+
const {closure,plugins}=snapshot;entries=snapshot.entries;
|
|
42
|
+
if(!portable&&!entries?.length)throw new FlowError('请至少选择一个工作流');
|
|
43
|
+
if(!['win32-x64','linux-x64-glibc'].includes(target))throw new FlowError('请选择支持的目标平台');
|
|
44
|
+
const ids=new Set(plugins.map(p=>p.id));
|
|
45
|
+
if(plugins.some(p=>!p.root))throw new FlowError('内存中临时定义的插件不能打包,请先安装为标准包');
|
|
46
|
+
const overrides=structuredClone(snapshot.project.environments??{});
|
|
47
|
+
for(const [id,value] of Object.entries(environments))overrides[id]={...overrides[id],...value};
|
|
48
|
+
// 未选中的插件配置不进入导出,但显式拼错的覆盖项应报错。
|
|
49
|
+
for(const id of Object.keys(overrides))if(!ids.has(id)&&!Object.hasOwn(environments,id))delete overrides[id];
|
|
50
|
+
const environmentPlan=await planEnvironments(plugins,target,overrides);
|
|
51
|
+
return {closure,plugins,environmentPlan,overrides,entries,snapshot};
|
|
52
|
+
}
|
|
53
|
+
export async function planApplication(app,options={}){
|
|
54
|
+
const {plugins,closure,environmentPlan,entries,snapshot}=await prepare(app,options);
|
|
55
|
+
return {target:options.target,entries,plugins:plugins.map(p=>({id:p.id,platforms:p.platforms,architectures:p.architectures})),network:snapshot.project.network,privileges:declaredPrivileges(closure,plugins),environments:environmentPlan.entries,environmentBytes:environmentPlan.bytes,warnings:environmentPlan.warnings};
|
|
56
|
+
}
|
|
57
|
+
export async function packApplication(app,{entries,target,outDir='dist',includeWeb=false,runtimePath,cacheDir=path.resolve('.cache','runtimes'),readOnly=false,portable=false,environments={}}={}){
|
|
58
|
+
const prepared=await prepare(app,{entries,target,includeWeb,portable,environments});
|
|
59
|
+
const {closure,plugins,environmentPlan,overrides,snapshot}=prepared;entries=prepared.entries;
|
|
60
|
+
const nodes=await collectPackages([snapshot.toolRoot,...plugins.map(p=>p.root)],{omitOptionalPeers:readOnly?['@mytools/pack']:[]});
|
|
61
|
+
const name=`fishtools-${target}-${Date.now()}-${randomUUID().slice(0,6)}`;
|
|
62
|
+
const directory=path.resolve(outDir,name);await mkdir(directory,{recursive:true});
|
|
63
|
+
const packages=await copyPackages(nodes,path.join(directory,'app'),target,{filters:environmentPlan.filters});
|
|
64
|
+
let runtime;
|
|
65
|
+
const base=snapshot.project.baseBundle?.root;
|
|
66
|
+
if(base){
|
|
67
|
+
const previous=JSON.parse(await readFile(path.join(base,'manifest.json'),'utf8'));
|
|
68
|
+
if(previous.target===target&&(readOnly||previous.runtime.npm)){
|
|
69
|
+
if(!(await verifyBundle(base)).ok)throw new FlowError('原分发包完整性校验失败,不能复用运行时');
|
|
70
|
+
await cp(path.join(base,'runtime'),path.join(directory,'runtime'),{recursive:true,dereference:true});
|
|
71
|
+
await cp(path.join(base,'licenses'),path.join(directory,'licenses'),{recursive:true});runtime=previous.runtime;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
runtime??=await copyRuntime({target,directory,cacheDir,runtimePath,includeNpm:!readOnly});
|
|
75
|
+
runtime={...runtime,npm:!readOnly||!!runtime.npm};
|
|
76
|
+
const workflowDir=path.join(directory,'workflows');await mkdir(workflowDir,{recursive:true});
|
|
77
|
+
// 保存来源文件,保持子流程相对引用;不打包未选中的流程。
|
|
78
|
+
for(const w of closure.workflows)await writeFile(path.join(workflowDir,`${w.id}.yaml`),YAML.stringify(w));
|
|
79
|
+
const config={};
|
|
80
|
+
for(const p of plugins)if(p.defaultConfig)config[p.id]=p.defaultConfig;
|
|
81
|
+
await writeFile(path.join(directory,'flow.project.json'),JSON.stringify({readOnly,plugins:plugins.map(p=>`./app/node_modules/${p.id}`),config,entries,environments:overrides,network:snapshot.project.network,logs:snapshot.project.logs},null,2));
|
|
82
|
+
const bundleId=randomUUID();
|
|
83
|
+
await writeFile(path.join(directory,'bundle.json'),JSON.stringify({format:2,id:bundleId,editable:!readOnly}));
|
|
84
|
+
await writeFile(path.join(directory,'package.json'),JSON.stringify({private:true,type:'module'},null,2));
|
|
85
|
+
const launch=target==='win32-x64'
|
|
86
|
+
? '@echo off\r\n"%~dp0runtime\\node.exe" "%~dp0app\\node_modules\\@mytools\\fst-cli\\bin\\fst.js" --project "%~dp0." %*\r\n'
|
|
87
|
+
: '#!/bin/sh\nROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)\nexec "$ROOT/runtime/node" "$ROOT/app/node_modules/@mytools/fst-cli/bin/fst.js" --project "$ROOT" "$@"\n';
|
|
88
|
+
const launcher=path.join(directory,target==='win32-x64'?'run.cmd':'run.sh');await writeFile(launcher,launch);await chmod(launcher,0o755);
|
|
89
|
+
const fst=path.join(directory,target==='win32-x64'?'fst.cmd':'fst');await writeFile(fst,launch);await chmod(fst,0o755);
|
|
90
|
+
await mkdir(path.join(directory,'bin'),{recursive:true});
|
|
91
|
+
const binLaunch=target==='win32-x64'?'@echo off\r\ncall "%~dp0..\\fst.cmd" %*\r\n':'#!/bin/sh\nexec "$(dirname -- "$0")/../fst" "$@"\n';
|
|
92
|
+
await writeFile(path.join(directory,'bin',target==='win32-x64'?'fst.cmd':'fst'),binLaunch);
|
|
93
|
+
if(includeWeb){const web=target==='win32-x64'?'@echo off\r\ncall "%~dp0run.cmd" web %*\r\n':'#!/bin/sh\nexec "$(dirname -- "$0")/run.sh" web "$@"\n';const f=path.join(directory,target==='win32-x64'?'web.cmd':'web.sh');await writeFile(f,web);await chmod(f,0o755);}
|
|
94
|
+
const executablePaths=new Set(['runtime/node','fst','bin/fst']);
|
|
95
|
+
for(const e of environmentPlan.entries.filter(e=>e.source==='bundled'))for(const p of packages.filter(p=>p.name===e.plugin)){
|
|
96
|
+
const file=['app',p.path,e.executable].join('/');executablePaths.add(file);await chmod(path.join(directory,file),0o755);
|
|
97
|
+
}
|
|
98
|
+
await copyDocumentation(path.join(directory,'manual'));
|
|
99
|
+
const manifest={format:2,bundleId,editable:!readOnly,createdAt:new Date().toISOString(),target,runtime,entries,workflows:closure.workflows.map(w=>w.id),plugins:plugins.map(p=>({id:p.id,version:p.version,platforms:p.platforms,architectures:p.architectures})),network:snapshot.project.network,privileges:declaredPrivileges(closure,plugins),environments:environmentPlan.entries,warnings:environmentPlan.warnings,packages,files:await hashes(directory)};
|
|
100
|
+
await writeFile(path.join(directory,'manifest.json'),JSON.stringify(manifest,null,2));
|
|
101
|
+
const archive=await archiveDirectory(directory,target,executablePaths);
|
|
102
|
+
return {directory,archive,manifest};
|
|
103
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mytools/pack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"*.js"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@mytools/sdk": "0.1.0",
|
|
12
|
+
"@npmcli/arborist": "10.0.3",
|
|
13
|
+
"archiver": "8.0.0",
|
|
14
|
+
"make-fetch-happen": "16.0.1",
|
|
15
|
+
"npm-packlist": "11.3.0",
|
|
16
|
+
"tar": "7.5.22",
|
|
17
|
+
"yaml": "2.9.1",
|
|
18
|
+
"@mytools/fst-platform": "0.1.0",
|
|
19
|
+
"@mytools/fst-devtools": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./index.js",
|
|
23
|
+
"./dependencies": "./dependencies.js",
|
|
24
|
+
"./runtime": "./runtime.js",
|
|
25
|
+
"./environments": "./environments.js",
|
|
26
|
+
"./archive": "./archive.js",
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, copyFile, chmod,cp } from 'node:fs/promises';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import * as tar from 'tar';
|
|
5
|
+
import fetch from 'make-fetch-happen';
|
|
6
|
+
import { FlowError } from '@mytools/sdk';
|
|
7
|
+
|
|
8
|
+
export const NODE_VERSION='24.15.0';
|
|
9
|
+
const hashes={
|
|
10
|
+
'win32-x64':'3331e1ffe19874215472217c5e94f5a0c6d8e18c4ac7111d3937aa0ad5e9b4a5',
|
|
11
|
+
'linux-x64-glibc':'44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89',
|
|
12
|
+
};
|
|
13
|
+
export const digest=data=>createHash('sha256').update(data).digest('hex');
|
|
14
|
+
async function download(url){const response=await fetch(url,{timeout:120000,retry:{retries:2}});if(!response.ok)throw new FlowError(`下载失败 ${response.status}:${url}`);return Buffer.from(await response.arrayBuffer());}
|
|
15
|
+
|
|
16
|
+
export async function copyRuntime({target,directory,cacheDir,runtimePath,includeNpm=false}){
|
|
17
|
+
if(!hashes[target])throw new FlowError(`尚不支持目标:${target}`);
|
|
18
|
+
await mkdir(path.join(directory,'runtime'),{recursive:true});
|
|
19
|
+
await mkdir(cacheDir,{recursive:true});
|
|
20
|
+
const win=target==='win32-x64';
|
|
21
|
+
const file=win?'win-x64/node.exe':`node-v${NODE_VERSION}-linux-x64.tar.gz`;
|
|
22
|
+
const cache=path.join(cacheDir,file.replaceAll('/','-'));
|
|
23
|
+
let bytes;
|
|
24
|
+
if(runtimePath){
|
|
25
|
+
if(!win)throw new FlowError('自定义 runtimePath 首版只接受已验证的 Windows Node.js 文件');
|
|
26
|
+
bytes=await readFile(runtimePath);
|
|
27
|
+
}else{
|
|
28
|
+
try{bytes=await readFile(cache);}catch{
|
|
29
|
+
bytes=await download(`https://nodejs.org/download/release/v${NODE_VERSION}/${file}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if(digest(bytes)!==hashes[target])throw new FlowError('Node.js 运行时校验失败,请检查缓存或指定文件');
|
|
33
|
+
await writeFile(cache,bytes);
|
|
34
|
+
if(win)await writeFile(path.join(directory,'runtime','node.exe'),bytes);
|
|
35
|
+
else{
|
|
36
|
+
const extraction=path.join(cacheDir,'linux-extracted');await mkdir(extraction,{recursive:true});
|
|
37
|
+
await tar.x({file:cache,cwd:extraction,filter:name=>name.endsWith('/bin/node')||name.endsWith('/LICENSE')});
|
|
38
|
+
await copyFile(path.join(extraction,`node-v${NODE_VERSION}-linux-x64/bin/node`),path.join(directory,'runtime','node'));
|
|
39
|
+
await chmod(path.join(directory,'runtime','node'),0o755);
|
|
40
|
+
}
|
|
41
|
+
const licenseCache=path.join(cacheDir,'NODE_LICENSE');
|
|
42
|
+
let license;
|
|
43
|
+
try{license=await readFile(licenseCache);}catch{
|
|
44
|
+
try{if(process.versions.node!==NODE_VERSION)throw new Error('版本不同');license=await readFile(path.join(path.dirname(process.execPath),'LICENSE'));}
|
|
45
|
+
catch{license=await download(`https://raw.githubusercontent.com/nodejs/node/v${NODE_VERSION}/LICENSE`);}
|
|
46
|
+
await writeFile(licenseCache,license);
|
|
47
|
+
}
|
|
48
|
+
await mkdir(path.join(directory,'licenses'),{recursive:true});await writeFile(path.join(directory,'licenses','NODE_LICENSE'),license);
|
|
49
|
+
if(includeNpm){
|
|
50
|
+
// npm 是平台无关的 JS,直接复用已固定校验的官方 Linux 分发中的完整 npm 目录。
|
|
51
|
+
const npmArchive=path.join(cacheDir,`node-v${NODE_VERSION}-linux-x64.tar.gz`);
|
|
52
|
+
let npmBytes;
|
|
53
|
+
try{npmBytes=await readFile(npmArchive);}catch{npmBytes=await download(`https://nodejs.org/download/release/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz`);}
|
|
54
|
+
if(digest(npmBytes)!==hashes['linux-x64-glibc'])throw new FlowError('npm 来源的 Node.js 归档校验失败');
|
|
55
|
+
await writeFile(npmArchive,npmBytes);
|
|
56
|
+
const extraction=path.join(cacheDir,'npm-extracted');await mkdir(extraction,{recursive:true});
|
|
57
|
+
await tar.x({file:npmArchive,cwd:extraction,filter:name=>name.includes('/lib/node_modules/npm/')});
|
|
58
|
+
await cp(path.join(extraction,`node-v${NODE_VERSION}-linux-x64/lib/node_modules/npm`),path.join(directory,'runtime/node_modules/npm'),{recursive:true,dereference:true});
|
|
59
|
+
}
|
|
60
|
+
return {version:NODE_VERSION,source:`https://nodejs.org/download/release/v${NODE_VERSION}/${file}`,sha256:hashes[target]};
|
|
61
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# SDK模块
|
|
2
|
+
|
|
3
|
+
包名:`@mytools/sdk`。内部 workspace 包,随 `@yanglaofish/fishtools` 统一交付。
|
|
4
|
+
|
|
5
|
+
## 职责与边界
|
|
6
|
+
|
|
7
|
+
插件定义、错误和共享 schema/条件契约。不承接宿主生命周期、磁盘持久化或界面行为。
|
|
8
|
+
|
|
9
|
+
内部依赖:无。
|
|
10
|
+
## 公开入口
|
|
11
|
+
|
|
12
|
+
| 导入路径 | 实现位置 |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `@mytools/sdk` | `./index.js` |
|
|
15
|
+
| `@mytools/sdk/schema` | `./validation.js` |
|
|
16
|
+
| `@mytools/sdk/schema-path` | `./schema-path.js` |
|
|
17
|
+
| `@mytools/sdk/diagnostics` | `./diagnostics.js` |
|
|
18
|
+
| `@mytools/sdk/conditions` | `./conditions.js` |
|
|
19
|
+
| `@mytools/sdk/package.json` | `./package.json` |
|
|
20
|
+
|
|
21
|
+
仅依赖 exports 声明的入口,禁止消费者跨包读取 src/bin 私有文件。入口路径由本模块维护,修改协议时同步消费方回归。
|
|
22
|
+
|
|
23
|
+
## 维护与验证
|
|
24
|
+
|
|
25
|
+
本模块负责上述职责的实现、文档与回归;跨模块接口调整需要同时检查生产者与消费者。具体维护人员尚未指定。
|
|
26
|
+
|
|
27
|
+
在仓库根目录执行:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
node --test tests/modules.test.js tests/plugin-contract.test.js tests/schema-diagnostics.test.js
|
|
31
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const conditionOperators=['eq','ne','gt','gte','lt','lte','contains','exists'];
|