shelfware 0.0.1 → 0.1.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/.output/nitro.json +15 -0
- package/.output/public/_nuxt/6IstVYrV.js +1 -0
- package/.output/public/_nuxt/9C5WUlLE.js +36 -0
- package/.output/public/_nuxt/BDNMzG2s.js +1 -0
- package/.output/public/_nuxt/BW3xJdEx.js +1 -0
- package/.output/public/_nuxt/BXXtFil5.js +1 -0
- package/.output/public/_nuxt/BZuUK3cr.js +1 -0
- package/.output/public/_nuxt/BeLyMmd4.js +1 -0
- package/.output/public/_nuxt/C_VgD9ZI.js +3 -0
- package/.output/public/_nuxt/DB5Vxbte.js +2 -0
- package/.output/public/_nuxt/DMg0j7gv.js +14 -0
- package/.output/public/_nuxt/builds/latest.json +1 -0
- package/.output/public/_nuxt/builds/meta/4c307968-4837-497b-a570-d6ee336accc4.json +1 -0
- package/.output/public/_nuxt/entry.b3_DJTEt.css +2 -0
- package/.output/public/_nuxt/error-404.CHsn7XF-.css +1 -0
- package/.output/public/_nuxt/error-500.Bawx2bLr.css +1 -0
- package/.output/server/chunks/_/api-handler.mjs +44 -0
- package/.output/server/chunks/_/batch.mjs +27 -0
- package/.output/server/chunks/_/catalog.mjs +1115 -0
- package/.output/server/chunks/_/detail.mjs +16 -0
- package/.output/server/chunks/_/error-500.mjs +19 -0
- package/.output/server/chunks/_/quarantine.mjs +159 -0
- package/.output/server/chunks/nitro/nitro.mjs +5399 -0
- package/.output/server/chunks/routes/api/_..._.mjs +16 -0
- package/.output/server/chunks/routes/api/health.get.mjs +13 -0
- package/.output/server/chunks/routes/api/skills/_id/file.get.mjs +25 -0
- package/.output/server/chunks/routes/api/skills/_id_.get.mjs +25 -0
- package/.output/server/chunks/routes/api/skills/_id_.put.mjs +74 -0
- package/.output/server/chunks/routes/api/skills/delete.post.mjs +48 -0
- package/.output/server/chunks/routes/api/skills/quarantine.post.mjs +26 -0
- package/.output/server/chunks/routes/api/skills/restore.post.mjs +26 -0
- package/.output/server/chunks/routes/api/skills.get.mjs +41 -0
- package/.output/server/chunks/routes/renderer.mjs +341 -0
- package/.output/server/chunks/virtual/_virtual_spa-template.mjs +3 -0
- package/.output/server/chunks/virtual/precomputed.mjs +3 -0
- package/.output/server/index.mjs +9 -0
- package/.output/server/node_modules/@babel/parser/lib/index.js +14611 -0
- package/.output/server/node_modules/@babel/parser/package.json +50 -0
- package/.output/server/node_modules/@vue/compiler-core/dist/compiler-core.cjs.prod.js +6801 -0
- package/.output/server/node_modules/@vue/compiler-core/package.json +58 -0
- package/.output/server/node_modules/@vue/compiler-dom/dist/compiler-dom.cjs.prod.js +692 -0
- package/.output/server/node_modules/@vue/compiler-dom/package.json +57 -0
- package/.output/server/node_modules/@vue/compiler-ssr/dist/compiler-ssr.cjs.js +1420 -0
- package/.output/server/node_modules/@vue/compiler-ssr/package.json +34 -0
- package/.output/server/node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js +1903 -0
- package/.output/server/node_modules/@vue/reactivity/package.json +55 -0
- package/.output/server/node_modules/@vue/runtime-core/dist/runtime-core.cjs.prod.js +6980 -0
- package/.output/server/node_modules/@vue/runtime-core/package.json +52 -0
- package/.output/server/node_modules/@vue/runtime-dom/dist/runtime-dom.cjs.prod.js +1867 -0
- package/.output/server/node_modules/@vue/runtime-dom/package.json +60 -0
- package/.output/server/node_modules/@vue/server-renderer/dist/server-renderer.cjs.prod.js +904 -0
- package/.output/server/node_modules/@vue/server-renderer/package.json +53 -0
- package/.output/server/node_modules/@vue/shared/dist/shared.cjs.prod.js +636 -0
- package/.output/server/node_modules/@vue/shared/package.json +47 -0
- package/.output/server/node_modules/devalue/index.js +17 -0
- package/.output/server/node_modules/devalue/package.json +39 -0
- package/.output/server/node_modules/devalue/src/base64.js +60 -0
- package/.output/server/node_modules/devalue/src/constants.js +12 -0
- package/.output/server/node_modules/devalue/src/operations.js +193 -0
- package/.output/server/node_modules/devalue/src/parse.js +274 -0
- package/.output/server/node_modules/devalue/src/stringify.js +428 -0
- package/.output/server/node_modules/devalue/src/uneval.js +606 -0
- package/.output/server/node_modules/devalue/src/utils.js +185 -0
- package/.output/server/node_modules/entities/dist/commonjs/decode-codepoint.js +77 -0
- package/.output/server/node_modules/entities/dist/commonjs/decode.js +568 -0
- package/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-html.js +7 -0
- package/.output/server/node_modules/entities/dist/commonjs/generated/decode-data-xml.js +7 -0
- package/.output/server/node_modules/entities/dist/commonjs/internal/bin-trie-flags.js +21 -0
- package/.output/server/node_modules/entities/dist/commonjs/internal/decode-shared.js +31 -0
- package/.output/server/node_modules/entities/dist/commonjs/package.json +3 -0
- package/.output/server/node_modules/entities/package.json +120 -0
- package/.output/server/node_modules/estree-walker/dist/umd/estree-walker.js +344 -0
- package/.output/server/node_modules/estree-walker/package.json +37 -0
- package/.output/server/node_modules/hookable/dist/index.mjs +257 -0
- package/.output/server/node_modules/hookable/package.json +53 -0
- package/.output/server/node_modules/nostics/dist/formatters/ansi.mjs +19 -0
- package/.output/server/node_modules/nostics/dist/index.mjs +187 -0
- package/.output/server/node_modules/nostics/package.json +125 -0
- package/.output/server/node_modules/source-map-js/lib/array-set.js +121 -0
- package/.output/server/node_modules/source-map-js/lib/base64-vlq.js +140 -0
- package/.output/server/node_modules/source-map-js/lib/base64.js +67 -0
- package/.output/server/node_modules/source-map-js/lib/binary-search.js +111 -0
- package/.output/server/node_modules/source-map-js/lib/mapping-list.js +79 -0
- package/.output/server/node_modules/source-map-js/lib/quick-sort.js +132 -0
- package/.output/server/node_modules/source-map-js/lib/source-map-consumer.js +1188 -0
- package/.output/server/node_modules/source-map-js/lib/source-map-generator.js +444 -0
- package/.output/server/node_modules/source-map-js/lib/source-node.js +413 -0
- package/.output/server/node_modules/source-map-js/lib/util.js +594 -0
- package/.output/server/node_modules/source-map-js/package.json +71 -0
- package/.output/server/node_modules/source-map-js/source-map.js +8 -0
- package/.output/server/node_modules/ufo/dist/index.mjs +645 -0
- package/.output/server/node_modules/ufo/package.json +48 -0
- package/.output/server/node_modules/unhead/dist/client.mjs +22 -0
- package/.output/server/node_modules/unhead/dist/legacy.mjs +33 -0
- package/.output/server/node_modules/unhead/dist/minify.mjs +183 -0
- package/.output/server/node_modules/unhead/dist/parser.mjs +518 -0
- package/.output/server/node_modules/unhead/dist/plugins.mjs +720 -0
- package/.output/server/node_modules/unhead/dist/server.mjs +99 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.-hZVKou0.mjs +400 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.BEmVMkeA.mjs +673 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.BGFxPGPQ.mjs +47 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.BQWSw36o.mjs +192 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Bf_fPVYA.mjs +6 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Bjbp1C8D.mjs +224 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Bm4Y6XQI.mjs +17 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.C5Bksi2B.mjs +49 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.CGPOfp5O.mjs +32 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.CHEy9ana.mjs +7 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.D7HkBzZn.mjs +59 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.Dssd7L5I.mjs +196 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.jEy9BmRq.mjs +264 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.oZ7pkTV1.mjs +19 -0
- package/.output/server/node_modules/unhead/dist/shared/unhead.tQnRaXfX.mjs +240 -0
- package/.output/server/node_modules/unhead/dist/stream/client.mjs +33 -0
- package/.output/server/node_modules/unhead/dist/stream/iife.mjs +2 -0
- package/.output/server/node_modules/unhead/dist/stream/server.mjs +445 -0
- package/.output/server/node_modules/unhead/dist/utils.mjs +7 -0
- package/.output/server/node_modules/unhead/package.json +179 -0
- package/.output/server/node_modules/vue/dist/vue.cjs.js +80 -0
- package/.output/server/node_modules/vue/dist/vue.cjs.prod.js +66 -0
- package/.output/server/node_modules/vue/index.js +7 -0
- package/.output/server/node_modules/vue/index.mjs +1 -0
- package/.output/server/node_modules/vue/package.json +112 -0
- package/.output/server/node_modules/vue/server-renderer/index.mjs +1 -0
- package/.output/server/node_modules/vue-bundle-renderer/dist/runtime.mjs +278 -0
- package/.output/server/node_modules/vue-bundle-renderer/package.json +54 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/.output/server/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/.output/server/node_modules/yaml/dist/compose/composer.js +224 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
- package/.output/server/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/.output/server/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/.output/server/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/.output/server/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/.output/server/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/.output/server/node_modules/yaml/dist/doc/Document.js +337 -0
- package/.output/server/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/.output/server/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/.output/server/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/.output/server/node_modules/yaml/dist/doc/directives.js +178 -0
- package/.output/server/node_modules/yaml/dist/errors.js +62 -0
- package/.output/server/node_modules/yaml/dist/index.js +50 -0
- package/.output/server/node_modules/yaml/dist/log.js +19 -0
- package/.output/server/node_modules/yaml/dist/nodes/Alias.js +118 -0
- package/.output/server/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/.output/server/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/.output/server/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/.output/server/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/.output/server/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/.output/server/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/.output/server/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/.output/server/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/.output/server/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/.output/server/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/.output/server/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/.output/server/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/.output/server/node_modules/yaml/dist/parse/cst.js +112 -0
- package/.output/server/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/.output/server/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/.output/server/node_modules/yaml/dist/parse/parser.js +980 -0
- package/.output/server/node_modules/yaml/dist/public-api.js +107 -0
- package/.output/server/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/.output/server/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/.output/server/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/.output/server/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/.output/server/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/.output/server/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/.output/server/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/.output/server/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/.output/server/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/.output/server/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/.output/server/node_modules/yaml/dist/schema/tags.js +99 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/.output/server/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/.output/server/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/.output/server/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/.output/server/node_modules/yaml/dist/visit.js +236 -0
- package/.output/server/node_modules/yaml/package.json +97 -0
- package/.output/server/package.json +28 -0
- package/LICENSE +21 -0
- package/README.md +57 -1
- package/bin/launch.mjs +158 -0
- package/bin/shelfware.mjs +6 -0
- package/package.json +64 -5
- package/cli.mjs +0 -3
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var identity = require('./nodes/identity.js');
|
|
4
|
+
|
|
5
|
+
const BREAK = Symbol('break visit');
|
|
6
|
+
const SKIP = Symbol('skip children');
|
|
7
|
+
const REMOVE = Symbol('remove node');
|
|
8
|
+
/**
|
|
9
|
+
* Apply a visitor to an AST node or document.
|
|
10
|
+
*
|
|
11
|
+
* Walks through the tree (depth-first) starting from `node`, calling a
|
|
12
|
+
* `visitor` function with three arguments:
|
|
13
|
+
* - `key`: For sequence values and map `Pair`, the node's index in the
|
|
14
|
+
* collection. Within a `Pair`, `'key'` or `'value'`, correspondingly.
|
|
15
|
+
* `null` for the root node.
|
|
16
|
+
* - `node`: The current node.
|
|
17
|
+
* - `path`: The ancestry of the current node.
|
|
18
|
+
*
|
|
19
|
+
* The return value of the visitor may be used to control the traversal:
|
|
20
|
+
* - `undefined` (default): Do nothing and continue
|
|
21
|
+
* - `visit.SKIP`: Do not visit the children of this node, continue with next
|
|
22
|
+
* sibling
|
|
23
|
+
* - `visit.BREAK`: Terminate traversal completely
|
|
24
|
+
* - `visit.REMOVE`: Remove the current node, then continue with the next one
|
|
25
|
+
* - `Node`: Replace the current node, then continue by visiting it
|
|
26
|
+
* - `number`: While iterating the items of a sequence or map, set the index
|
|
27
|
+
* of the next step. This is useful especially if the index of the current
|
|
28
|
+
* node has changed.
|
|
29
|
+
*
|
|
30
|
+
* If `visitor` is a single function, it will be called with all values
|
|
31
|
+
* encountered in the tree, including e.g. `null` values. Alternatively,
|
|
32
|
+
* separate visitor functions may be defined for each `Map`, `Pair`, `Seq`,
|
|
33
|
+
* `Alias` and `Scalar` node. To define the same visitor function for more than
|
|
34
|
+
* one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar)
|
|
35
|
+
* and `Node` (alias, map, seq & scalar) targets. Of all these, only the most
|
|
36
|
+
* specific defined one will be used for each node.
|
|
37
|
+
*/
|
|
38
|
+
function visit(node, visitor) {
|
|
39
|
+
const visitor_ = initVisitor(visitor);
|
|
40
|
+
if (identity.isDocument(node)) {
|
|
41
|
+
const cd = visit_(null, node.contents, visitor_, Object.freeze([node]));
|
|
42
|
+
if (cd === REMOVE)
|
|
43
|
+
node.contents = null;
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
visit_(null, node, visitor_, Object.freeze([]));
|
|
47
|
+
}
|
|
48
|
+
// Without the `as symbol` casts, TS declares these in the `visit`
|
|
49
|
+
// namespace using `var`, but then complains about that because
|
|
50
|
+
// `unique symbol` must be `const`.
|
|
51
|
+
/** Terminate visit traversal completely */
|
|
52
|
+
visit.BREAK = BREAK;
|
|
53
|
+
/** Do not visit the children of the current node */
|
|
54
|
+
visit.SKIP = SKIP;
|
|
55
|
+
/** Remove the current node */
|
|
56
|
+
visit.REMOVE = REMOVE;
|
|
57
|
+
function visit_(key, node, visitor, path) {
|
|
58
|
+
const ctrl = callVisitor(key, node, visitor, path);
|
|
59
|
+
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
60
|
+
replaceNode(key, path, ctrl);
|
|
61
|
+
return visit_(key, ctrl, visitor, path);
|
|
62
|
+
}
|
|
63
|
+
if (typeof ctrl !== 'symbol') {
|
|
64
|
+
if (identity.isCollection(node)) {
|
|
65
|
+
path = Object.freeze(path.concat(node));
|
|
66
|
+
for (let i = 0; i < node.items.length; ++i) {
|
|
67
|
+
const ci = visit_(i, node.items[i], visitor, path);
|
|
68
|
+
if (typeof ci === 'number')
|
|
69
|
+
i = ci - 1;
|
|
70
|
+
else if (ci === BREAK)
|
|
71
|
+
return BREAK;
|
|
72
|
+
else if (ci === REMOVE) {
|
|
73
|
+
node.items.splice(i, 1);
|
|
74
|
+
i -= 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (identity.isPair(node)) {
|
|
79
|
+
path = Object.freeze(path.concat(node));
|
|
80
|
+
const ck = visit_('key', node.key, visitor, path);
|
|
81
|
+
if (ck === BREAK)
|
|
82
|
+
return BREAK;
|
|
83
|
+
else if (ck === REMOVE)
|
|
84
|
+
node.key = null;
|
|
85
|
+
const cv = visit_('value', node.value, visitor, path);
|
|
86
|
+
if (cv === BREAK)
|
|
87
|
+
return BREAK;
|
|
88
|
+
else if (cv === REMOVE)
|
|
89
|
+
node.value = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return ctrl;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Apply an async visitor to an AST node or document.
|
|
96
|
+
*
|
|
97
|
+
* Walks through the tree (depth-first) starting from `node`, calling a
|
|
98
|
+
* `visitor` function with three arguments:
|
|
99
|
+
* - `key`: For sequence values and map `Pair`, the node's index in the
|
|
100
|
+
* collection. Within a `Pair`, `'key'` or `'value'`, correspondingly.
|
|
101
|
+
* `null` for the root node.
|
|
102
|
+
* - `node`: The current node.
|
|
103
|
+
* - `path`: The ancestry of the current node.
|
|
104
|
+
*
|
|
105
|
+
* The return value of the visitor may be used to control the traversal:
|
|
106
|
+
* - `Promise`: Must resolve to one of the following values
|
|
107
|
+
* - `undefined` (default): Do nothing and continue
|
|
108
|
+
* - `visit.SKIP`: Do not visit the children of this node, continue with next
|
|
109
|
+
* sibling
|
|
110
|
+
* - `visit.BREAK`: Terminate traversal completely
|
|
111
|
+
* - `visit.REMOVE`: Remove the current node, then continue with the next one
|
|
112
|
+
* - `Node`: Replace the current node, then continue by visiting it
|
|
113
|
+
* - `number`: While iterating the items of a sequence or map, set the index
|
|
114
|
+
* of the next step. This is useful especially if the index of the current
|
|
115
|
+
* node has changed.
|
|
116
|
+
*
|
|
117
|
+
* If `visitor` is a single function, it will be called with all values
|
|
118
|
+
* encountered in the tree, including e.g. `null` values. Alternatively,
|
|
119
|
+
* separate visitor functions may be defined for each `Map`, `Pair`, `Seq`,
|
|
120
|
+
* `Alias` and `Scalar` node. To define the same visitor function for more than
|
|
121
|
+
* one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar)
|
|
122
|
+
* and `Node` (alias, map, seq & scalar) targets. Of all these, only the most
|
|
123
|
+
* specific defined one will be used for each node.
|
|
124
|
+
*/
|
|
125
|
+
async function visitAsync(node, visitor) {
|
|
126
|
+
const visitor_ = initVisitor(visitor);
|
|
127
|
+
if (identity.isDocument(node)) {
|
|
128
|
+
const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node]));
|
|
129
|
+
if (cd === REMOVE)
|
|
130
|
+
node.contents = null;
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
await visitAsync_(null, node, visitor_, Object.freeze([]));
|
|
134
|
+
}
|
|
135
|
+
// Without the `as symbol` casts, TS declares these in the `visit`
|
|
136
|
+
// namespace using `var`, but then complains about that because
|
|
137
|
+
// `unique symbol` must be `const`.
|
|
138
|
+
/** Terminate visit traversal completely */
|
|
139
|
+
visitAsync.BREAK = BREAK;
|
|
140
|
+
/** Do not visit the children of the current node */
|
|
141
|
+
visitAsync.SKIP = SKIP;
|
|
142
|
+
/** Remove the current node */
|
|
143
|
+
visitAsync.REMOVE = REMOVE;
|
|
144
|
+
async function visitAsync_(key, node, visitor, path) {
|
|
145
|
+
const ctrl = await callVisitor(key, node, visitor, path);
|
|
146
|
+
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
147
|
+
replaceNode(key, path, ctrl);
|
|
148
|
+
return visitAsync_(key, ctrl, visitor, path);
|
|
149
|
+
}
|
|
150
|
+
if (typeof ctrl !== 'symbol') {
|
|
151
|
+
if (identity.isCollection(node)) {
|
|
152
|
+
path = Object.freeze(path.concat(node));
|
|
153
|
+
for (let i = 0; i < node.items.length; ++i) {
|
|
154
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path);
|
|
155
|
+
if (typeof ci === 'number')
|
|
156
|
+
i = ci - 1;
|
|
157
|
+
else if (ci === BREAK)
|
|
158
|
+
return BREAK;
|
|
159
|
+
else if (ci === REMOVE) {
|
|
160
|
+
node.items.splice(i, 1);
|
|
161
|
+
i -= 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (identity.isPair(node)) {
|
|
166
|
+
path = Object.freeze(path.concat(node));
|
|
167
|
+
const ck = await visitAsync_('key', node.key, visitor, path);
|
|
168
|
+
if (ck === BREAK)
|
|
169
|
+
return BREAK;
|
|
170
|
+
else if (ck === REMOVE)
|
|
171
|
+
node.key = null;
|
|
172
|
+
const cv = await visitAsync_('value', node.value, visitor, path);
|
|
173
|
+
if (cv === BREAK)
|
|
174
|
+
return BREAK;
|
|
175
|
+
else if (cv === REMOVE)
|
|
176
|
+
node.value = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return ctrl;
|
|
180
|
+
}
|
|
181
|
+
function initVisitor(visitor) {
|
|
182
|
+
if (typeof visitor === 'object' &&
|
|
183
|
+
(visitor.Collection || visitor.Node || visitor.Value)) {
|
|
184
|
+
return Object.assign({
|
|
185
|
+
Alias: visitor.Node,
|
|
186
|
+
Map: visitor.Node,
|
|
187
|
+
Scalar: visitor.Node,
|
|
188
|
+
Seq: visitor.Node
|
|
189
|
+
}, visitor.Value && {
|
|
190
|
+
Map: visitor.Value,
|
|
191
|
+
Scalar: visitor.Value,
|
|
192
|
+
Seq: visitor.Value
|
|
193
|
+
}, visitor.Collection && {
|
|
194
|
+
Map: visitor.Collection,
|
|
195
|
+
Seq: visitor.Collection
|
|
196
|
+
}, visitor);
|
|
197
|
+
}
|
|
198
|
+
return visitor;
|
|
199
|
+
}
|
|
200
|
+
function callVisitor(key, node, visitor, path) {
|
|
201
|
+
if (typeof visitor === 'function')
|
|
202
|
+
return visitor(key, node, path);
|
|
203
|
+
if (identity.isMap(node))
|
|
204
|
+
return visitor.Map?.(key, node, path);
|
|
205
|
+
if (identity.isSeq(node))
|
|
206
|
+
return visitor.Seq?.(key, node, path);
|
|
207
|
+
if (identity.isPair(node))
|
|
208
|
+
return visitor.Pair?.(key, node, path);
|
|
209
|
+
if (identity.isScalar(node))
|
|
210
|
+
return visitor.Scalar?.(key, node, path);
|
|
211
|
+
if (identity.isAlias(node))
|
|
212
|
+
return visitor.Alias?.(key, node, path);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
function replaceNode(key, path, node) {
|
|
216
|
+
const parent = path[path.length - 1];
|
|
217
|
+
if (identity.isCollection(parent)) {
|
|
218
|
+
parent.items[key] = node;
|
|
219
|
+
}
|
|
220
|
+
else if (identity.isPair(parent)) {
|
|
221
|
+
if (key === 'key')
|
|
222
|
+
parent.key = node;
|
|
223
|
+
else
|
|
224
|
+
parent.value = node;
|
|
225
|
+
}
|
|
226
|
+
else if (identity.isDocument(parent)) {
|
|
227
|
+
parent.contents = node;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const pt = identity.isAlias(parent) ? 'alias' : 'scalar';
|
|
231
|
+
throw new Error(`Cannot replace node with ${pt} parent`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
exports.visit = visit;
|
|
236
|
+
exports.visitAsync = visitAsync;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yaml",
|
|
3
|
+
"version": "2.9.0",
|
|
4
|
+
"license": "ISC",
|
|
5
|
+
"author": "Eemeli Aro <eemeli@gmail.com>",
|
|
6
|
+
"funding": "https://github.com/sponsors/eemeli",
|
|
7
|
+
"repository": "github:eemeli/yaml",
|
|
8
|
+
"description": "JavaScript parser and stringifier for YAML",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"YAML",
|
|
11
|
+
"parser",
|
|
12
|
+
"stringifier"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://eemeli.org/yaml/",
|
|
15
|
+
"files": [
|
|
16
|
+
"browser/",
|
|
17
|
+
"dist/",
|
|
18
|
+
"util.js"
|
|
19
|
+
],
|
|
20
|
+
"type": "commonjs",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"bin": "./bin.mjs",
|
|
23
|
+
"browser": {
|
|
24
|
+
"./dist/index.js": "./browser/index.js",
|
|
25
|
+
"./dist/util.js": "./browser/dist/util.js",
|
|
26
|
+
"./util.js": "./browser/dist/util.js"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"node": "./dist/index.js",
|
|
32
|
+
"default": "./browser/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json",
|
|
35
|
+
"./util": {
|
|
36
|
+
"types": "./dist/util.d.ts",
|
|
37
|
+
"node": "./dist/util.js",
|
|
38
|
+
"default": "./browser/dist/util.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "npm run build:node && npm run build:browser",
|
|
43
|
+
"build:browser": "rollup -c config/rollup.browser-config.mjs",
|
|
44
|
+
"build:node": "rollup -c config/rollup.node-config.mjs",
|
|
45
|
+
"clean": "git clean -fdxe node_modules",
|
|
46
|
+
"lint": "eslint config/ src/",
|
|
47
|
+
"prettier": "prettier --write .",
|
|
48
|
+
"prestart": "rollup --sourcemap -c config/rollup.node-config.mjs",
|
|
49
|
+
"start": "node --enable-source-maps -i -e 'YAML=require(\"./dist/index.js\");const{parse,parseDocument,parseAllDocuments}=YAML'",
|
|
50
|
+
"test": "jest --config config/jest.config.js",
|
|
51
|
+
"test:all": "npm test && npm run test:types && npm run test:dist && npm run test:dist:types",
|
|
52
|
+
"test:browsers": "cd playground && npm test",
|
|
53
|
+
"test:dist": "npm run build:node && jest --config config/jest.config.js",
|
|
54
|
+
"test:dist:types": "tsc --allowJs --moduleResolution node --noEmit --target es5 dist/index.js",
|
|
55
|
+
"test:types": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
|
|
56
|
+
"docs:install": "cd docs-slate && bundle install",
|
|
57
|
+
"predocs:deploy": "node docs/prepare-docs.mjs",
|
|
58
|
+
"docs:deploy": "cd docs-slate && ./deploy.sh",
|
|
59
|
+
"predocs": "node docs/prepare-docs.mjs",
|
|
60
|
+
"docs": "cd docs-slate && bundle exec middleman server",
|
|
61
|
+
"preversion": "npm test && npm run build",
|
|
62
|
+
"prepublishOnly": "npm run clean && npm test && npm run build"
|
|
63
|
+
},
|
|
64
|
+
"browserslist": "defaults, not ie 11",
|
|
65
|
+
"prettier": {
|
|
66
|
+
"arrowParens": "avoid",
|
|
67
|
+
"semi": false,
|
|
68
|
+
"singleQuote": true,
|
|
69
|
+
"trailingComma": "none"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@babel/core": "^7.12.10",
|
|
73
|
+
"@babel/plugin-transform-typescript": "^7.12.17",
|
|
74
|
+
"@babel/preset-env": "^7.12.11",
|
|
75
|
+
"@eslint/js": "^9.9.1",
|
|
76
|
+
"@rollup/plugin-babel": "^6.0.3",
|
|
77
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
78
|
+
"@rollup/plugin-typescript": "^12.1.1",
|
|
79
|
+
"@types/jest": "^29.2.4",
|
|
80
|
+
"@types/node": "^20.11.20",
|
|
81
|
+
"babel-jest": "^29.0.1",
|
|
82
|
+
"eslint": "^9.9.1",
|
|
83
|
+
"eslint-config-prettier": "^10.1.8",
|
|
84
|
+
"fast-check": "^2.12.0",
|
|
85
|
+
"jest": "^29.0.1",
|
|
86
|
+
"jest-resolve": "^29.7.0",
|
|
87
|
+
"jest-ts-webcompat-resolver": "^1.0.0",
|
|
88
|
+
"prettier": "^3.0.2",
|
|
89
|
+
"rollup": "^4.12.0",
|
|
90
|
+
"tslib": "^2.8.1",
|
|
91
|
+
"typescript": "^5.7.2",
|
|
92
|
+
"typescript-eslint": "^8.4.0"
|
|
93
|
+
},
|
|
94
|
+
"engines": {
|
|
95
|
+
"node": ">= 14.6"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shelfware-prod",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@babel/parser": "7.29.8",
|
|
8
|
+
"@vue/compiler-core": "3.5.42",
|
|
9
|
+
"@vue/compiler-dom": "3.5.42",
|
|
10
|
+
"@vue/compiler-ssr": "3.5.42",
|
|
11
|
+
"@vue/reactivity": "3.5.42",
|
|
12
|
+
"@vue/runtime-core": "3.5.42",
|
|
13
|
+
"@vue/runtime-dom": "3.5.42",
|
|
14
|
+
"@vue/server-renderer": "3.5.42",
|
|
15
|
+
"@vue/shared": "3.5.42",
|
|
16
|
+
"devalue": "5.9.2",
|
|
17
|
+
"entities": "7.0.1",
|
|
18
|
+
"estree-walker": "2.0.2",
|
|
19
|
+
"hookable": "6.1.1",
|
|
20
|
+
"nostics": "1.2.0",
|
|
21
|
+
"source-map-js": "1.2.1",
|
|
22
|
+
"ufo": "1.6.4",
|
|
23
|
+
"unhead": "3.4.0",
|
|
24
|
+
"vue": "3.5.42",
|
|
25
|
+
"vue-bundle-renderer": "2.3.2",
|
|
26
|
+
"yaml": "2.9.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Grygorii Liashenko
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
1
|
# shelfware
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
shelfware finds the shelfware in your skill drawers.
|
|
4
|
+
|
|
5
|
+
A local catalog of the AI-agent skills installed on this machine: every `~/.<tool>/skills` drawer (`~/.claude/skills`, `~/.cursor/skills`, `~/.codex/skills`, `~/.agents/skills`, a custom `~/.claude-work/skills`, …), Cursor plugins, Gemini Antigravity, Hermes profiles, and friends. It scans, lists, searches, renders, **edits**, audits, quarantines, restores, and permanently deletes skills, from a browser tab that only your machine can reach.
|
|
6
|
+
|
|
7
|
+
## Run it
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx shelfware@latest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Options: `--port <n>` (or `PORT`), `--no-open` (or `SHELFWARE_NO_OPEN=1`), `--help`. The server binds `127.0.0.1` on the first free port from 3781 and opens your browser. Stop it with Ctrl-C.
|
|
14
|
+
|
|
15
|
+
Node 20 or newer. No build step: the package ships the prebuilt app. For a permanent install, `npm i -g shelfware` and then `shelfware`.
|
|
16
|
+
|
|
17
|
+
### First run
|
|
18
|
+
|
|
19
|
+
Starting shelfware only reads. It walks the hidden directories directly under your home (`$HOME`), skipping caches and toolchains, and picks up every `skills/` (or `skill/`) folder it finds, plus the Cursor, Gemini Antigravity and Hermes locations above. Nothing on disk changes until you quarantine, restore, delete or save a skill yourself.
|
|
20
|
+
|
|
21
|
+
Skills outside your home directory (project-level `.claude/skills` inside a repository, an arbitrary path) are not scanned in v0.1; a `--root <path>` flag is planned. To scan a different home, run `HOME=/other/home npx shelfware@latest`.
|
|
22
|
+
|
|
23
|
+
### Troubleshooting
|
|
24
|
+
|
|
25
|
+
- **`npx shelfware` prints "coming soon".** Your npx cache still holds the `0.0.1` placeholder. Run `npx shelfware@latest` once; npx then caches the real release.
|
|
26
|
+
- **The browser did not open.** Copy the URL from the `shelfware at http://127.0.0.1:<port>` line, or start with `--no-open` and open it yourself.
|
|
27
|
+
- **Port in use.** Pass `--port <n>`; without it shelfware tries 3781 through 3800.
|
|
28
|
+
- **The catalog is empty.** Check that your skills live under `$HOME/.<tool>/skills`; other locations are not scanned yet.
|
|
29
|
+
|
|
30
|
+
## What you get
|
|
31
|
+
|
|
32
|
+
- **Drawers**: one per install scope, with counts; a house census (physical cards, copies and wasted bytes, references, broken links, a rough token total).
|
|
33
|
+
- **Cards**: kind, form (file / link / broken), origin with its certainty, copies, static audit risk, invocation mode, `~tokens`.
|
|
34
|
+
- **Reader**: rendered manuscript, raw source, file folio with text previews, and a raw editor for `SKILL.md` with an optimistic save (409 when the file changed on disk).
|
|
35
|
+
- **Quarantine**: cards move to `~/.skill-cabinet/quarantine/` and can be restored to the exact path they came from. Permanent delete only from the quarantine shelf. The manifest format is the one skill-cabinet uses, so both tools can read each other's trash.
|
|
36
|
+
- **Keyboard**: `/` find, `j`/`k` move, `x` mark, `q` quarantine, `r` restore, `d` delete, `e` edit, `⌘S` / `Ctrl+S` save.
|
|
37
|
+
|
|
38
|
+
## Security model
|
|
39
|
+
|
|
40
|
+
- Every request that reaches the app must carry a loopback `Host` (`127.0.0.1`, `localhost`, `[::1]`) on the bound port; anything else is refused. This closes DNS-rebinding reads.
|
|
41
|
+
- Every mutation must carry a loopback `Origin` and a per-run session token that only the served page knows.
|
|
42
|
+
- No CORS headers, ever. File reads and saves are contained to the skill's directory; symlinks are never followed for destructive actions.
|
|
43
|
+
- Nothing leaves the machine. There is no network feature.
|
|
44
|
+
|
|
45
|
+
## Credits
|
|
46
|
+
|
|
47
|
+
Scanner rules, audit heuristics and the quarantine format are ported from [skill-cabinet](https://github.com/subsy/skill-cabinet) (MIT), whose audit rules were adapted from [Adaptive Skills](https://github.com/wangsoft/Adaptive-Skills) (MIT). shelfware is a rebuild in Nuxt 4 with an editor and a hardened local security model.
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm install
|
|
53
|
+
pnpm dev # http://localhost:3000
|
|
54
|
+
pnpm test # unit + nuxt + e2e
|
|
55
|
+
pnpm build
|
|
56
|
+
pnpm pack:verify # packs and runs the tarball through npx
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
MIT.
|
package/bin/launch.mjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Pure launcher helpers for `npx shelfware`. Node built-ins only (spec §13.1).
|
|
2
|
+
import { spawn as spawnProcess } from 'node:child_process'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import http from 'node:http'
|
|
6
|
+
import net from 'node:net'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_PORT = 3781
|
|
11
|
+
export const PORT_ATTEMPTS = 20
|
|
12
|
+
export const HOST = '127.0.0.1'
|
|
13
|
+
|
|
14
|
+
export const HELP = `shelfware — a local catalog of the AI-agent skills on this machine
|
|
15
|
+
|
|
16
|
+
Usage: shelfware [--port <n>] [--no-open]
|
|
17
|
+
|
|
18
|
+
--port <n> bind 127.0.0.1:<n> instead of the first free port from ${DEFAULT_PORT}
|
|
19
|
+
--no-open do not open the browser (also SHELFWARE_NO_OPEN=1)
|
|
20
|
+
--help, -h print this help and exit
|
|
21
|
+
PORT=<n> same as --port
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
/** Spec §10.3: `sw_` + 32 random bytes as hex. The letter prefix keeps destr from casting it. */
|
|
25
|
+
export function makeToken() {
|
|
26
|
+
return `sw_${crypto.randomBytes(32).toString('hex')}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseArgs(argv = [], env = {}) {
|
|
30
|
+
let port = null
|
|
31
|
+
let open = env.SHELFWARE_NO_OPEN !== '1'
|
|
32
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
33
|
+
const arg = argv[i]
|
|
34
|
+
if (arg === '--no-open') {
|
|
35
|
+
open = false
|
|
36
|
+
} else if (arg === '--port') {
|
|
37
|
+
port = Number(argv[i + 1])
|
|
38
|
+
i += 1
|
|
39
|
+
} else if (arg.startsWith('--port=')) {
|
|
40
|
+
port = Number(arg.slice('--port='.length))
|
|
41
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
42
|
+
return { help: true, port: null, open }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (port === null && env.PORT) port = Number(env.PORT)
|
|
46
|
+
if (port !== null && (!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
47
|
+
throw new Error(`Invalid port: ${port}`)
|
|
48
|
+
}
|
|
49
|
+
return { help: false, port, open }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isPortFree(port, host = HOST) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const server = net.createServer()
|
|
55
|
+
server.unref()
|
|
56
|
+
server.once('error', () => resolve(false))
|
|
57
|
+
server.listen({ port, host }, () => server.close(() => resolve(true)))
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function pickPort(start = DEFAULT_PORT, { attempts = PORT_ATTEMPTS, host = HOST } = {}) {
|
|
62
|
+
for (let port = start; port < start + attempts; port += 1) {
|
|
63
|
+
if (await isPortFree(port, host)) return port
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`No free port between ${start} and ${start + attempts - 1} on ${host}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** GET /api/health with the loopback Host the server insists on (spec §10.1). */
|
|
69
|
+
export function probeHealth(port, host = HOST) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const req = http.get(
|
|
72
|
+
{ host, port, path: '/api/health', headers: { Host: `${host}:${port}` }, timeout: 1000 },
|
|
73
|
+
(res) => {
|
|
74
|
+
let body = ''
|
|
75
|
+
res.setEncoding('utf8')
|
|
76
|
+
res.on('data', (chunk) => {
|
|
77
|
+
body += chunk
|
|
78
|
+
})
|
|
79
|
+
res.on('end', () => resolve(res.statusCode === 200 && body.includes('"ok":true')))
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
req.on('error', () => resolve(false))
|
|
83
|
+
req.on('timeout', () => {
|
|
84
|
+
req.destroy()
|
|
85
|
+
resolve(false)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function waitForHealth(port, { timeoutMs = 10_000, intervalMs = 100, host = HOST, probe = probeHealth } = {}) {
|
|
91
|
+
const deadline = Date.now() + timeoutMs
|
|
92
|
+
while (Date.now() < deadline) {
|
|
93
|
+
if (await probe(port, host)) return true
|
|
94
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs))
|
|
95
|
+
}
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function openBrowser(url, { platform = process.platform, spawn = spawnProcess } = {}) {
|
|
100
|
+
const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open'
|
|
101
|
+
const args = platform === 'win32' ? ['/c', 'start', '', url] : [url]
|
|
102
|
+
// A missing browser opener (e.g. no xdg-open on a headless box) must never take
|
|
103
|
+
// the running server down with it: spawn can throw synchronously (rare) or emit
|
|
104
|
+
// an async 'error' (the common ENOENT case), and an unhandled 'error' on an
|
|
105
|
+
// EventEmitter is fatal. Swallow both.
|
|
106
|
+
try {
|
|
107
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true })
|
|
108
|
+
child.on?.('error', () => {})
|
|
109
|
+
child.unref?.()
|
|
110
|
+
} catch {
|
|
111
|
+
// ignore: failing to open a browser is not a launch failure
|
|
112
|
+
}
|
|
113
|
+
return { cmd, args }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function serverEntry(binDir = path.dirname(fileURLToPath(import.meta.url))) {
|
|
117
|
+
return path.resolve(binDir, '..', '.output', 'server', 'index.mjs')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Orchestration with injectable I/O so it can be unit-tested end to end:
|
|
122
|
+
* flags → port → env → import the prebuilt server → wait for health → open.
|
|
123
|
+
*/
|
|
124
|
+
export async function launch({
|
|
125
|
+
argv = process.argv.slice(2),
|
|
126
|
+
env = process.env,
|
|
127
|
+
importServer,
|
|
128
|
+
log = console.log,
|
|
129
|
+
exit = process.exit,
|
|
130
|
+
entry = serverEntry(),
|
|
131
|
+
spawn = spawnProcess,
|
|
132
|
+
} = {}) {
|
|
133
|
+
const args = parseArgs(argv, env)
|
|
134
|
+
if (args.help) {
|
|
135
|
+
log(HELP)
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
if (!fs.existsSync(entry)) {
|
|
139
|
+
log(`shelfware: missing ${entry}. This copy was not built; install the published package or run \`pnpm build\`.`)
|
|
140
|
+
exit(1)
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
const port = args.port ?? await pickPort(DEFAULT_PORT)
|
|
144
|
+
env.NUXT_PUBLIC_SHELFWARE_TOKEN = makeToken()
|
|
145
|
+
env.NITRO_HOST = HOST
|
|
146
|
+
env.NITRO_PORT = String(port)
|
|
147
|
+
env.NODE_ENV = 'production'
|
|
148
|
+
await importServer()
|
|
149
|
+
if (!(await waitForHealth(port))) {
|
|
150
|
+
log(`shelfware: the server did not answer on http://${HOST}:${port} within 10 s`)
|
|
151
|
+
exit(1)
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
const url = `http://${HOST}:${port}`
|
|
155
|
+
log(`shelfware at ${url}`)
|
|
156
|
+
if (args.open) openBrowser(url, { spawn })
|
|
157
|
+
return { port, url }
|
|
158
|
+
}
|