@jigging/jig 0.1.0-alpha.13 → 0.1.0-alpha.15
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 +29 -185
- package/dist/index.js +41 -2
- package/dist/project/author.d.ts +3 -0
- package/dist/project/commands.d.ts +10 -0
- package/libexec/evaluator/project-authoring-1.schema.json +40 -0
- package/libexec/evaluator/project-evaluator-sdk.bundle.js +41 -2
- package/libexec/installed-cli.js +6205 -5025
- package/libexec/linux-rootless-supervisor.js +8 -4
- package/libexec/preparation/bun-native-preparation-worker.js +44 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
# `@jigging/jig`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The developer alpha has one finite command surface:
|
|
6
|
-
|
|
7
|
-
```text
|
|
8
|
-
jig init --bare <directory>
|
|
9
|
-
jig review [project] [--yes]
|
|
10
|
-
jig run <flow:path|binding:id> [--input JSON|@FILE] [--attach NAME=DIR] [--out DIR] [--timeout DURATION]
|
|
11
|
-
jig --version
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
`jig --version` prints the built package version without opening a project or
|
|
15
|
-
acquiring a sandbox.
|
|
16
|
-
|
|
17
|
-
There is no `jig setup`. `review` and `run` transparently acquire the rootless
|
|
18
|
-
authority they need or fail closed.
|
|
3
|
+
Put reusable Agent methods to work with powers you approve. Choose your Agents,
|
|
4
|
+
combine specialists, inspect their results, and stop owned work.
|
|
19
5
|
|
|
20
6
|
## Install
|
|
21
7
|
|
|
@@ -23,183 +9,41 @@ authority they need or fail closed.
|
|
|
23
9
|
npm install --global @jigging/jig@alpha
|
|
24
10
|
```
|
|
25
11
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
before evaluator or Flow bytes execute, and revalidates it before each launch.
|
|
31
|
-
|
|
32
|
-
For a useful application, try [an issue becoming a tested patch](https://jig.md/guide/tested-patch).
|
|
33
|
-
It selects local source files and returns a patch with executed checks, without
|
|
34
|
-
changing the original repository.
|
|
35
|
-
|
|
36
|
-
## Supported host
|
|
37
|
-
|
|
38
|
-
The alpha has been independently tested on a provisioned Ubuntu 24.04 x86_64
|
|
39
|
-
host. Other Linux x86_64 hosts meeting the requirements below have not yet been
|
|
40
|
-
independently validated; Jig fails closed when a required capability is absent.
|
|
41
|
-
|
|
42
|
-
The alpha requires:
|
|
43
|
-
|
|
44
|
-
- Linux x86_64 with glibc 2.17 or newer and an SSE4.2-capable baseline CPU;
|
|
45
|
-
- Bubblewrap 0.12 or newer and GNU `readlink -f`, located as described below;
|
|
46
|
-
- cgroup v2 with delegated `cpu`, `memory`, and `pids` controllers;
|
|
47
|
-
- a systemd user manager with transient delegated scopes; and
|
|
48
|
-
- unprivileged user, mount, PID, network, IPC, UTS, and cgroup namespaces.
|
|
49
|
-
|
|
50
|
-
The glibc and SSE4.2 floors come from the selected Bun baseline runtime.
|
|
51
|
-
|
|
52
|
-
Host tools are resolved through `/usr/bin`, `/bin`, or the NixOS system profile
|
|
53
|
-
`/run/current-system/sw/bin`, never ambient `PATH`. An operator may select
|
|
54
|
-
Bubblewrap using an absolute `JIG_BWRAP_PATH`, including a shell-local Nix
|
|
55
|
-
dependency. It receives the same validation and never falls back on failure.
|
|
56
|
-
On NixOS, enable
|
|
57
|
-
`programs.nix-ld.enable` for the unmodified npm runtime. Jig uses the real glibc
|
|
58
|
-
loader at the system-managed `share/nix-ld/lib/ld.so` link, mounting only that
|
|
59
|
-
loader and its required libraries into Runs. It does not mount nix-ld or the
|
|
60
|
-
whole Nix store. Independent NixOS host conformance is not yet established.
|
|
61
|
-
|
|
62
|
-
Jig commands do not use `sudo`, download runtimes, or expose host control to
|
|
63
|
-
Flow code. Runtime installation is handled once by npm with the Jig package.
|
|
64
|
-
|
|
65
|
-
Flow Runs default to 30 seconds. `jig run --timeout DURATION` accepts a
|
|
66
|
-
positive integer followed by `ms`, `s`, `m`, or `h`, up to 24 hours. Flow
|
|
67
|
-
execution scopes remain fixed at 256 MiB aggregate memory, 64 aggregate PIDs,
|
|
68
|
-
and 50% of one CPU. Project evaluation is fixed at 3 seconds, 256 MiB, 64
|
|
69
|
-
PIDs, and 50% CPU. Locked dependency preparation is fixed at 60 seconds, 512
|
|
70
|
-
MiB, 64 PIDs, and one CPU. See the repository
|
|
71
|
-
[`SECURITY.md`](https://github.com/jiggy/jig/blob/main/SECURITY.md) for the
|
|
72
|
-
threat boundary and private reporting channel.
|
|
73
|
-
|
|
74
|
-
## Use
|
|
75
|
-
|
|
76
|
-
```console
|
|
77
|
-
jig init --bare my-project
|
|
78
|
-
cd my-project
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Place packages under `flows/<name>/`. Each package has exact-case `FLOW.md`
|
|
82
|
-
and, for this alpha, one `flow.ts`. The generated `jig.ts`
|
|
83
|
-
explicitly discovers `./flows` and `./bindings`.
|
|
12
|
+
The developer alpha requires Linux x86_64/glibc, Bubblewrap, and a systemd user
|
|
13
|
+
manager with delegated cgroup v2 controllers. See the
|
|
14
|
+
[complete supported-host requirements](https://jig.md/guide/#supported-host).
|
|
15
|
+
npm installs Jig's exact Bun runtime dependency alongside the package.
|
|
84
16
|
|
|
85
|
-
|
|
86
|
-
authoring API. Declare it exactly in the Flow's `package.json`, generate a text
|
|
87
|
-
`bun.lock` with Bun 1.3.3 and `bun install --lockfile-only`, then handle one
|
|
88
|
-
finite Run:
|
|
17
|
+
## Get started
|
|
89
18
|
|
|
90
|
-
|
|
91
|
-
|
|
19
|
+
[Create your first Flow](https://jig.md/guide/#your-first-flow), or try
|
|
20
|
+
[an issue becoming a tested patch](https://jig.md/guide/tested-patch): selected
|
|
21
|
+
local source becomes a reviewable patch with executed checks, while the original
|
|
22
|
+
repository stays unchanged.
|
|
92
23
|
|
|
93
|
-
|
|
94
|
-
outcome: "done",
|
|
95
|
-
output: { received: run.input },
|
|
96
|
-
}));
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
```console
|
|
100
|
-
jig review
|
|
101
|
-
jig run flow:flows/example --input '{}'
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
For a longer Run, add a bounded duration such as `--timeout 2m`. The timeout
|
|
105
|
-
starts when Jig accepts the root Run. Project acquisition happens before it;
|
|
106
|
-
mandatory fencing and cleanup may finish afterward.
|
|
107
|
-
|
|
108
|
-
`jig review` shows the complete proposed project change, including exact
|
|
109
|
-
current and proposed Package/1 content digests, and asks for approval. It is
|
|
110
|
-
not a source-file diff; inspect editable source with your normal tools. When
|
|
111
|
-
standard input and output are both terminals, Jig prompts for approval.
|
|
112
|
-
Otherwise it prints the review and exits with `JIG_APPROVAL_REQUIRED`; `--yes`
|
|
113
|
-
records an already explicit approval. The CLI keeps review and admission
|
|
114
|
-
mechanics internal.
|
|
115
|
-
|
|
116
|
-
A target is marked changed when its package identity or exact execution
|
|
117
|
-
evidence changes. Package digests appear once in the package section;
|
|
118
|
-
host-specific evidence remains private.
|
|
119
|
-
|
|
120
|
-
For ordinary dependencies, place `package.json` and text `bun.lock` beside
|
|
121
|
-
`flow.ts`; do not include `node_modules`. `bun install --lockfile-only` is the
|
|
122
|
-
author-side lock-generation step: it creates no project-local `node_modules`,
|
|
123
|
-
but may use the network and Bun's author-side cache. On the first `jig review`
|
|
124
|
-
after those inputs change, Jig fetches the frozen production artifacts and
|
|
125
|
-
materializes a private execution snapshot in a contained trusted preparation
|
|
126
|
-
process, using the fixed Bun runtime, the default npm registry, and no
|
|
127
|
-
lifecycle scripts. It reuses the Run containment and ownership mechanism but
|
|
128
|
-
deliberately has registry network access. A declined review may therefore have
|
|
129
|
-
materialized inert evidence without granting authority. Unsupported or
|
|
130
|
-
unlocked sources fail before admission.
|
|
131
|
-
|
|
132
|
-
Unreleased code can remain ordinary readable files inside the Flow package and
|
|
133
|
-
be imported relatively. Code shared from elsewhere must be copied or bundled
|
|
134
|
-
into the finished package before `jig review`; Jig does not follow symlinks or
|
|
135
|
-
resolve `file:`, `workspace:`, or Git dependencies and does not own that build
|
|
136
|
-
step.
|
|
137
|
-
|
|
138
|
-
A dependency-free package needs no `bun.lock`; omit it rather than preserving
|
|
139
|
-
an empty or stale lock. Optional package schemas are FLOW Schema/1 files and
|
|
140
|
-
must begin with the exact root declaration
|
|
141
|
-
`"$schema": "https://flow.jig.md/schemas/schema-1.json"`.
|
|
24
|
+
Jig has three project commands:
|
|
142
25
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
prepared tree only while its source and host evidence still match.
|
|
148
|
-
|
|
149
|
-
Bindings use an explicit target. A declaration may also give its package exact
|
|
150
|
-
child-Flow slots:
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
import { defineBinding } from "@jigging/jig";
|
|
154
|
-
|
|
155
|
-
export default defineBinding({
|
|
156
|
-
package: "./flows/reviewer",
|
|
157
|
-
slots: { research: "flow:flows/research" },
|
|
158
|
-
});
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
```console
|
|
162
|
-
jig run binding:reviewer --input '{}'
|
|
26
|
+
```text
|
|
27
|
+
jig init --bare <directory>
|
|
28
|
+
jig review [project] [--allow-resolution-network] [--yes]
|
|
29
|
+
jig run <flow:path|binding:id> [options]
|
|
163
30
|
```
|
|
164
31
|
|
|
165
|
-
`
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Selected child Bindings must have no child slots of their own.
|
|
169
|
-
|
|
170
|
-
Each `flow/call` carries JSON/1 input into a fresh child context and returns a
|
|
171
|
-
complete JSON/1 Run result. The child receives its selected target's admitted
|
|
172
|
-
settings and Agent capability, empty attachments, no inherited slots, and a
|
|
173
|
-
deadline no later than its parent. It selects its own package-local Skills per
|
|
174
|
-
Agent call; providers and credentials remain host choices. Run/1 controls
|
|
175
|
-
operation identity, duplicate joins, conflicting reuse, cancellation, and
|
|
176
|
-
uncertainty; Jig does not replay uncertain dispatch automatically. There is no
|
|
177
|
-
separate child history, administration, scheduler, catalogue, or resolver. Jig
|
|
178
|
-
also never guesses an unprefixed target.
|
|
179
|
-
|
|
180
|
-
Each Run context admits one active operation; a child Flow can own one Agent
|
|
181
|
-
operation while its parent awaits it. Excess distinct concurrent calls receive
|
|
182
|
-
`RESOURCE_EXHAUSTED`, while sequential calls remain available. Cancellation and
|
|
183
|
-
cleanup cover the specialist and its locally owned Agent worker.
|
|
32
|
+
`review` asks you to approve the proposed project changes. `run` executes the
|
|
33
|
+
admitted revision and returns JSON after settling owned work. Use
|
|
34
|
+
`jig --version` to inspect the installed version.
|
|
184
35
|
|
|
185
|
-
|
|
186
|
-
available through ordinary Run/1 `effect/call`. The host may use the official
|
|
187
|
-
OpenAI JavaScript SDK against an operator-selected OpenAI-compatible endpoint,
|
|
188
|
-
or run native Codex, Claude Code, or Pi through one private ACP mechanism.
|
|
189
|
-
Direct API configuration uses `OPENAI_API_KEY` and `OPENAI_MODEL`; optional
|
|
190
|
-
`OPENAI_BASE_URL` and `OPENAI_API` select the HTTPS endpoint and either the
|
|
191
|
-
`responses` (default) or `chat-completions` wire shape. Jig supplies no default
|
|
192
|
-
model. Client, API, endpoint, model, executable path, and credentials are
|
|
193
|
-
trusted host configuration, not FLOW or Binding inputs, and Jig exposes no
|
|
194
|
-
provider registry.
|
|
36
|
+
## Guides
|
|
195
37
|
|
|
196
|
-
|
|
197
|
-
[
|
|
198
|
-
[
|
|
38
|
+
- [Choose an Agent](https://jig.md/guide/agents)
|
|
39
|
+
- [Working with files](https://jig.md/guide/files)
|
|
40
|
+
- [Dependencies](https://jig.md/guide/dependencies)
|
|
41
|
+
- [Workflow design](https://jig.md/guide/workflow-design)
|
|
42
|
+
- [Project authoring](https://jig.md/spec/project-sdk)
|
|
43
|
+
- [Execution policy](https://jig.md/spec/project-policy)
|
|
44
|
+
- [Security boundary](https://github.com/jiggy/jig/blob/main/SECURITY.md)
|
|
199
45
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
Semantic Choice, Jig Graph, schedulers, catalogues, runtime registries, or
|
|
203
|
-
sandbox registries.
|
|
46
|
+
Jig is prerelease software. [FLOW](https://flow.jig.md/) remains an independent
|
|
47
|
+
standard for portable methods.
|
|
204
48
|
|
|
205
49
|
Copyright © 2026 Victor Duarte <zvictor> and contributors.
|
package/dist/index.js
CHANGED
|
@@ -105,6 +105,43 @@ function validateString(value, byteLimit) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// src/project/commands.ts
|
|
109
|
+
function projectCommandPath(value) {
|
|
110
|
+
if (typeof value !== "string" || value.length > 256 || !/^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value) || value.split("/").some((part) => part === "" || part === "." || part === "..") || value.split("/").length > 16)
|
|
111
|
+
throw new TypeError("command file paths must be bounded relative paths without traversal");
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function normalizeProjectCommands(value) {
|
|
115
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
116
|
+
throw new TypeError("commands must be an object");
|
|
117
|
+
const entries = Object.entries(value);
|
|
118
|
+
if (entries.length > 8)
|
|
119
|
+
throw new TypeError("commands exceed eight entries");
|
|
120
|
+
const output = Object.create(null);
|
|
121
|
+
for (const [name, item] of entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
122
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64)
|
|
123
|
+
throw new TypeError("command name must be a LocalName");
|
|
124
|
+
if (item === null || typeof item !== "object" || Array.isArray(item) || Object.keys(item).length !== 1)
|
|
125
|
+
throw new TypeError(`command ${name} must contain run or test`);
|
|
126
|
+
const command = item;
|
|
127
|
+
if (Object.hasOwn(command, "run")) {
|
|
128
|
+
const path = projectCommandPath(command.run);
|
|
129
|
+
if (!path.endsWith(".ts") && !path.endsWith(".js"))
|
|
130
|
+
throw new TypeError("command run must name a .ts or .js entrypoint");
|
|
131
|
+
output[name] = Object.freeze({ run: path });
|
|
132
|
+
} else if (Object.hasOwn(command, "test") && Array.isArray(command.test)) {
|
|
133
|
+
if (command.test.length === 0 || command.test.length > 16)
|
|
134
|
+
throw new TypeError("command test must select one to sixteen test files");
|
|
135
|
+
const paths = command.test.map(projectCommandPath);
|
|
136
|
+
if (new Set(paths).size !== paths.length || paths.some((path) => !/\.(test|spec)\.(ts|js)$/.test(path)))
|
|
137
|
+
throw new TypeError("command test must name distinct .test or .spec files");
|
|
138
|
+
output[name] = Object.freeze({ test: Object.freeze(paths) });
|
|
139
|
+
} else
|
|
140
|
+
throw new TypeError(`command ${name} must contain run or test`);
|
|
141
|
+
}
|
|
142
|
+
return Object.freeze(output);
|
|
143
|
+
}
|
|
144
|
+
|
|
108
145
|
// src/package/case-fold-15.1.ts
|
|
109
146
|
var CASE_FOLD_15_1 = new Map([
|
|
110
147
|
[65, "a"],
|
|
@@ -1797,7 +1834,7 @@ function bindingRef(id) {
|
|
|
1797
1834
|
}
|
|
1798
1835
|
function normalizeBinding(input, canonical) {
|
|
1799
1836
|
const captured = snapshotJsonObject(input, "Binding definition");
|
|
1800
|
-
assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots"] : ["package", "settings", "slots"], "Binding definition");
|
|
1837
|
+
assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots", "commands"] : ["package", "settings", "slots", "commands"], "Binding definition");
|
|
1801
1838
|
if (canonical && captured.kind !== "package") {
|
|
1802
1839
|
throw new TypeError("Binding kind must be package");
|
|
1803
1840
|
}
|
|
@@ -1806,11 +1843,13 @@ function normalizeBinding(input, canonical) {
|
|
|
1806
1843
|
const packagePath = normalizeProjectPath(captured.package, "package");
|
|
1807
1844
|
const settings = Object.hasOwn(captured, "settings") ? expectJsonObject(captured.settings, "settings") : emptyRecord();
|
|
1808
1845
|
const slots = Object.hasOwn(captured, "slots") ? normalizeFlowSlots(captured.slots) : emptyRecord();
|
|
1846
|
+
const commands = normalizeProjectCommands(Object.hasOwn(captured, "commands") ? captured.commands : {});
|
|
1809
1847
|
return record({
|
|
1810
1848
|
kind: "package",
|
|
1811
1849
|
package: packagePath,
|
|
1812
1850
|
settings,
|
|
1813
|
-
slots
|
|
1851
|
+
slots,
|
|
1852
|
+
...Object.keys(commands).length === 0 ? {} : { commands }
|
|
1814
1853
|
});
|
|
1815
1854
|
}
|
|
1816
1855
|
function normalizeFlowSlots(value) {
|
package/dist/project/author.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JsonObject } from '../json.js';
|
|
2
|
+
import { type ProjectCommands } from './commands.js';
|
|
2
3
|
export interface DiscoverySource {
|
|
3
4
|
readonly kind: 'discover';
|
|
4
5
|
readonly roots: readonly string[];
|
|
@@ -31,11 +32,13 @@ export interface PackageBindingDefinition {
|
|
|
31
32
|
readonly package: string;
|
|
32
33
|
readonly settings: JsonObject;
|
|
33
34
|
readonly slots: Readonly<Record<string, string>>;
|
|
35
|
+
readonly commands?: ProjectCommands;
|
|
34
36
|
}
|
|
35
37
|
export interface PackageBindingInput {
|
|
36
38
|
readonly package: string;
|
|
37
39
|
readonly settings?: JsonObject;
|
|
38
40
|
readonly slots?: Readonly<Record<string, string>>;
|
|
41
|
+
readonly commands?: ProjectCommands;
|
|
39
42
|
}
|
|
40
43
|
export type BindingDefinition = PackageBindingDefinition;
|
|
41
44
|
export declare function discover(roots: string | readonly string[]): DiscoverySource;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Reviewed invocation policy; runtime and containment remain host choices. */
|
|
2
|
+
export type ProjectCommand = {
|
|
3
|
+
readonly run: string;
|
|
4
|
+
} | {
|
|
5
|
+
readonly test: readonly string[];
|
|
6
|
+
};
|
|
7
|
+
export type ProjectCommands = Readonly<Record<string, ProjectCommand>>;
|
|
8
|
+
export declare function projectCommandPath(value: unknown): string;
|
|
9
|
+
/** Input must already be an ordinary JSON snapshot at the owning boundary. */
|
|
10
|
+
export declare function normalizeProjectCommands(value: unknown): ProjectCommands;
|
|
@@ -102,6 +102,9 @@
|
|
|
102
102
|
"$ref": "#/$defs/targetSelector"
|
|
103
103
|
},
|
|
104
104
|
"maxProperties": 256
|
|
105
|
+
},
|
|
106
|
+
"commands": {
|
|
107
|
+
"$ref": "#/$defs/commands"
|
|
105
108
|
}
|
|
106
109
|
},
|
|
107
110
|
"required": ["kind", "package", "settings", "slots"],
|
|
@@ -109,6 +112,43 @@
|
|
|
109
112
|
},
|
|
110
113
|
"bindingDefinition": {
|
|
111
114
|
"$ref": "#/$defs/packageBinding"
|
|
115
|
+
},
|
|
116
|
+
"commands": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"maxProperties": 8,
|
|
119
|
+
"additionalProperties": {
|
|
120
|
+
"oneOf": [
|
|
121
|
+
{
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"run": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"minLength": 1,
|
|
127
|
+
"maxLength": 256
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"required": ["run"],
|
|
131
|
+
"additionalProperties": false
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {
|
|
136
|
+
"test": {
|
|
137
|
+
"type": "array",
|
|
138
|
+
"minItems": 1,
|
|
139
|
+
"maxItems": 16,
|
|
140
|
+
"items": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"minLength": 1,
|
|
143
|
+
"maxLength": 256
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"required": ["test"],
|
|
148
|
+
"additionalProperties": false
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
112
152
|
}
|
|
113
153
|
}
|
|
114
154
|
}
|
|
@@ -105,6 +105,43 @@ function validateString(value, byteLimit) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// src/project/commands.ts
|
|
109
|
+
function projectCommandPath(value) {
|
|
110
|
+
if (typeof value !== "string" || value.length > 256 || !/^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(value) || value.split("/").some((part) => part === "" || part === "." || part === "..") || value.split("/").length > 16)
|
|
111
|
+
throw new TypeError("command file paths must be bounded relative paths without traversal");
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function normalizeProjectCommands(value) {
|
|
115
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
116
|
+
throw new TypeError("commands must be an object");
|
|
117
|
+
const entries = Object.entries(value);
|
|
118
|
+
if (entries.length > 8)
|
|
119
|
+
throw new TypeError("commands exceed eight entries");
|
|
120
|
+
const output = Object.create(null);
|
|
121
|
+
for (const [name, item] of entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
122
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64)
|
|
123
|
+
throw new TypeError("command name must be a LocalName");
|
|
124
|
+
if (item === null || typeof item !== "object" || Array.isArray(item) || Object.keys(item).length !== 1)
|
|
125
|
+
throw new TypeError(`command ${name} must contain run or test`);
|
|
126
|
+
const command = item;
|
|
127
|
+
if (Object.hasOwn(command, "run")) {
|
|
128
|
+
const path = projectCommandPath(command.run);
|
|
129
|
+
if (!path.endsWith(".ts") && !path.endsWith(".js"))
|
|
130
|
+
throw new TypeError("command run must name a .ts or .js entrypoint");
|
|
131
|
+
output[name] = Object.freeze({ run: path });
|
|
132
|
+
} else if (Object.hasOwn(command, "test") && Array.isArray(command.test)) {
|
|
133
|
+
if (command.test.length === 0 || command.test.length > 16)
|
|
134
|
+
throw new TypeError("command test must select one to sixteen test files");
|
|
135
|
+
const paths = command.test.map(projectCommandPath);
|
|
136
|
+
if (new Set(paths).size !== paths.length || paths.some((path) => !/\.(test|spec)\.(ts|js)$/.test(path)))
|
|
137
|
+
throw new TypeError("command test must name distinct .test or .spec files");
|
|
138
|
+
output[name] = Object.freeze({ test: Object.freeze(paths) });
|
|
139
|
+
} else
|
|
140
|
+
throw new TypeError(`command ${name} must contain run or test`);
|
|
141
|
+
}
|
|
142
|
+
return Object.freeze(output);
|
|
143
|
+
}
|
|
144
|
+
|
|
108
145
|
// src/package/case-fold-15.1.ts
|
|
109
146
|
var CASE_FOLD_15_1 = new Map([
|
|
110
147
|
[65, "a"],
|
|
@@ -1797,7 +1834,7 @@ function bindingRef(id) {
|
|
|
1797
1834
|
}
|
|
1798
1835
|
function normalizeBinding(input, canonical) {
|
|
1799
1836
|
const captured = snapshotJsonObject(input, "Binding definition");
|
|
1800
|
-
assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots"] : ["package", "settings", "slots"], "Binding definition");
|
|
1837
|
+
assertClosedObject(captured, canonical ? ["kind", "package", "settings", "slots", "commands"] : ["package", "settings", "slots", "commands"], "Binding definition");
|
|
1801
1838
|
if (canonical && captured.kind !== "package") {
|
|
1802
1839
|
throw new TypeError("Binding kind must be package");
|
|
1803
1840
|
}
|
|
@@ -1806,11 +1843,13 @@ function normalizeBinding(input, canonical) {
|
|
|
1806
1843
|
const packagePath = normalizeProjectPath(captured.package, "package");
|
|
1807
1844
|
const settings = Object.hasOwn(captured, "settings") ? expectJsonObject(captured.settings, "settings") : emptyRecord();
|
|
1808
1845
|
const slots = Object.hasOwn(captured, "slots") ? normalizeFlowSlots(captured.slots) : emptyRecord();
|
|
1846
|
+
const commands = normalizeProjectCommands(Object.hasOwn(captured, "commands") ? captured.commands : {});
|
|
1809
1847
|
return record({
|
|
1810
1848
|
kind: "package",
|
|
1811
1849
|
package: packagePath,
|
|
1812
1850
|
settings,
|
|
1813
|
-
slots
|
|
1851
|
+
slots,
|
|
1852
|
+
...Object.keys(commands).length === 0 ? {} : { commands }
|
|
1814
1853
|
});
|
|
1815
1854
|
}
|
|
1816
1855
|
function normalizeFlowSlots(value) {
|