@teurons/vds 0.0.2 → 0.0.4
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 +8 -7
- package/dist/vds.js +125 -10
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The `vds` command. It starts, checks and indexes a VDS workspace on your machine
|
|
|
4
4
|
|
|
5
5
|
VDS keeps a product's spec as files: capabilities that hold rules written in EARS,
|
|
6
6
|
scenarios in Gherkin, and flows that point at those rules in order.
|
|
7
|
-
This package is an early alpha
|
|
7
|
+
This package is an early alpha.
|
|
8
8
|
|
|
9
9
|
## Set it up with your AI agent
|
|
10
10
|
|
|
@@ -13,10 +13,10 @@ Copy this prompt into Claude Code, Codex or any coding agent, run from the root
|
|
|
13
13
|
```text
|
|
14
14
|
Set up VDS in this project.
|
|
15
15
|
|
|
16
|
-
1. Run `npx -y @teurons/vds
|
|
16
|
+
1. Run `npx -y @teurons/vds init vds-workspace`. It creates the
|
|
17
17
|
capabilities, flows and scenarios folders, a VDS.md guide and a .vds.json.
|
|
18
18
|
If it says a workspace already exists, stop and tell me what it found.
|
|
19
|
-
2. Add `"vds": "vds"` to package.json scripts and @teurons/vds
|
|
19
|
+
2. Add `"vds": "vds"` to package.json scripts and @teurons/vds to
|
|
20
20
|
devDependencies if this project uses npm, pnpm or yarn. Otherwise skip this.
|
|
21
21
|
3. Read vds-workspace/VDS.md and follow it from now on whenever you work on
|
|
22
22
|
the spec.
|
|
@@ -24,16 +24,17 @@ Set up VDS in this project.
|
|
|
24
24
|
what exists) saying: the product spec lives in vds-workspace; read
|
|
25
25
|
vds-workspace/VDS.md before changing it; run `vds index vds-workspace`
|
|
26
26
|
first and `vds check vds-workspace` before finishing.
|
|
27
|
-
5. Run `npx @teurons/vds
|
|
27
|
+
5. Run `npx @teurons/vds check vds-workspace` and confirm it prints valid.
|
|
28
28
|
6. Tell me what you created and changed, file by file.
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Commands
|
|
32
32
|
|
|
33
33
|
```sh
|
|
34
|
-
npx @teurons/vds
|
|
35
|
-
npx @teurons/vds
|
|
36
|
-
npx @teurons/vds
|
|
34
|
+
npx @teurons/vds init [folder] # start a workspace (never overwrites one)
|
|
35
|
+
npx @teurons/vds check [folder] # print every problem; exit 1 if any
|
|
36
|
+
npx @teurons/vds index [folder] # print the index: every flow and rule, one line each
|
|
37
|
+
npx @teurons/vds update [folder] # install the newest vds, refresh VDS.md
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
`folder` defaults to the current folder.
|
package/dist/vds.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin.js
|
|
4
|
-
import { readdirSync, readFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { readdirSync, readFileSync as readFileSync4 } from "node:fs";
|
|
5
|
+
import { join as join3, resolve } from "node:path";
|
|
6
6
|
|
|
7
7
|
// ../../specs/js/load.js
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
@@ -97,8 +97,18 @@ function loadMapper() {
|
|
|
97
97
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
98
98
|
import { createServer } from "node:net";
|
|
99
99
|
import { join } from "node:path";
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
|
|
101
|
+
// src/version.js
|
|
102
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
103
|
+
var baked = false ? null : "0.0.4";
|
|
104
|
+
var VERSION = process.env.VDS_VERSION ?? baked ?? JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
105
|
+
function compare(a, b) {
|
|
106
|
+
const [x, y] = [a, b].map((v) => v.split("-")[0].split(".").map(Number));
|
|
107
|
+
for (let i = 0; i < 3; i++) if ((x[i] || 0) !== (y[i] || 0)) return (x[i] || 0) - (y[i] || 0);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/guide.js
|
|
102
112
|
var GUIDE = `# Working in this VDS workspace
|
|
103
113
|
|
|
104
114
|
This folder holds the product's requirements, flows and scenarios as plain files.
|
|
@@ -118,6 +128,15 @@ Read and write them with the vds command.
|
|
|
118
128
|
4. Point flows at requirement ids. A state lists the ids it needs under \`requirements:\`.
|
|
119
129
|
5. Run \`vds check\` before you finish. It prints "valid" when nothing is wrong; fix every problem it lists.
|
|
120
130
|
`;
|
|
131
|
+
var markerFor = (version) => `<!-- vds ${version} -->`;
|
|
132
|
+
function guideText() {
|
|
133
|
+
return `${markerFor(VERSION)}
|
|
134
|
+
${GUIDE}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/init.js
|
|
138
|
+
var FOLDERS = ["capabilities", "flows", "scenarios"];
|
|
139
|
+
var FIRST_PORT = 4600;
|
|
121
140
|
async function freePort() {
|
|
122
141
|
for (let port = FIRST_PORT; ; port++) {
|
|
123
142
|
const free = await new Promise((resolve2) => {
|
|
@@ -136,12 +155,98 @@ async function init(root) {
|
|
|
136
155
|
}
|
|
137
156
|
const port = await freePort();
|
|
138
157
|
for (const dir of FOLDERS) mkdirSync(join(root, dir), { recursive: true });
|
|
139
|
-
writeFileSync(join(root, "VDS.md"),
|
|
158
|
+
writeFileSync(join(root, "VDS.md"), guideText());
|
|
140
159
|
writeFileSync(join(root, ".vds.json"), `${JSON.stringify({ port })}
|
|
141
160
|
`);
|
|
142
161
|
return 0;
|
|
143
162
|
}
|
|
144
163
|
|
|
164
|
+
// src/update.js
|
|
165
|
+
import { spawnSync } from "node:child_process";
|
|
166
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
167
|
+
import { dirname, join as join2 } from "node:path";
|
|
168
|
+
import { fileURLToPath } from "node:url";
|
|
169
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
170
|
+
var MARKER = /^<!-- vds (\d+\.\d+\.\d+\S*) -->/;
|
|
171
|
+
async function newestVersion() {
|
|
172
|
+
const base = process.env.VDS_REGISTRY ?? REGISTRY;
|
|
173
|
+
try {
|
|
174
|
+
const res = await fetch(`${base}/@teurons%2fvds`);
|
|
175
|
+
if (!res.ok) throw new Error(`status ${res.status}`);
|
|
176
|
+
return (await res.json())["dist-tags"].latest;
|
|
177
|
+
} catch (e) {
|
|
178
|
+
throw new Error(`could not reach the npm registry: ${e.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function installKind() {
|
|
182
|
+
const file = fileURLToPath(import.meta.url);
|
|
183
|
+
if (file.includes("/_npx/")) return { kind: "npx" };
|
|
184
|
+
const at = file.indexOf("/node_modules/");
|
|
185
|
+
const root = at > 0 ? file.slice(0, at) : null;
|
|
186
|
+
if (root && existsSync2(join2(root, "package.json"))) return { kind: "project", root };
|
|
187
|
+
return { kind: "global" };
|
|
188
|
+
}
|
|
189
|
+
function installPlan(version) {
|
|
190
|
+
const spec = `@teurons/vds@${version}`;
|
|
191
|
+
const found = installKind();
|
|
192
|
+
if (found.kind === "project") {
|
|
193
|
+
const has = (f) => existsSync2(join2(found.root, f));
|
|
194
|
+
const cmd = has("pnpm-lock.yaml") ? `pnpm add -D ${spec}` : has("yarn.lock") ? `yarn add -D ${spec}` : `npm install -D ${spec}`;
|
|
195
|
+
return { ...found, cmd, bin: join2(found.root, "node_modules", ".bin", "vds") };
|
|
196
|
+
}
|
|
197
|
+
return { ...found, cmd: `npm install -g ${spec}`, bin: "vds" };
|
|
198
|
+
}
|
|
199
|
+
function refreshGuide(folder) {
|
|
200
|
+
const path = join2(folder, "VDS.md");
|
|
201
|
+
if (!existsSync2(path)) return false;
|
|
202
|
+
const current = readFileSync3(path, "utf8");
|
|
203
|
+
const marked = current.match(MARKER)?.[1];
|
|
204
|
+
if (marked && compare(marked, VERSION) >= 0) return false;
|
|
205
|
+
const next = guideText();
|
|
206
|
+
if (next === current) return false;
|
|
207
|
+
writeFileSync2(path, next);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
function runShell(cmd, version, cwd) {
|
|
211
|
+
return spawnSync(cmd, {
|
|
212
|
+
shell: true,
|
|
213
|
+
stdio: "inherit",
|
|
214
|
+
cwd,
|
|
215
|
+
env: { ...process.env, VDS_TARGET_VERSION: version }
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async function update(folder, { guideOnly = false } = {}) {
|
|
219
|
+
if (guideOnly) {
|
|
220
|
+
if (refreshGuide(folder)) console.log(`VDS.md rewritten for vds ${VERSION}`);
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
const newest = await newestVersion();
|
|
224
|
+
if (compare(newest, VERSION) > 0) {
|
|
225
|
+
const plan = installPlan(newest);
|
|
226
|
+
if (plan.kind === "npx" && !process.env.VDS_INSTALL_CMD) {
|
|
227
|
+
console.log("npx already fetches the newest vds each run; use npx @teurons/vds@latest.");
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
const cmd = process.env.VDS_INSTALL_CMD ?? plan.cmd;
|
|
231
|
+
if (runShell(cmd, newest, plan.root).status !== 0) {
|
|
232
|
+
console.error(`install failed: ${cmd}`);
|
|
233
|
+
return 1;
|
|
234
|
+
}
|
|
235
|
+
console.log(`vds updated from ${VERSION} to ${newest}`);
|
|
236
|
+
const bin = process.env.VDS_BIN ?? plan.bin;
|
|
237
|
+
const r = spawnSync(bin, ["update", "--guide-only", folder], { stdio: "inherit" });
|
|
238
|
+
if (r.status !== 0)
|
|
239
|
+
console.error(`could not refresh VDS.md with vds ${newest}; run vds update again`);
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
if (refreshGuide(folder)) {
|
|
243
|
+
console.log(`vds ${VERSION} is the newest; VDS.md rewritten to match it`);
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
console.log(`everything is current: vds ${VERSION} is the newest and ${folder} is up to date`);
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
145
250
|
// src/bin.js
|
|
146
251
|
var FOLDERS2 = ["capabilities", "flows", "scenarios"];
|
|
147
252
|
function readWorkspace(root) {
|
|
@@ -149,16 +254,19 @@ function readWorkspace(root) {
|
|
|
149
254
|
for (const dir of FOLDERS2) {
|
|
150
255
|
let names = [];
|
|
151
256
|
try {
|
|
152
|
-
names = readdirSync(
|
|
257
|
+
names = readdirSync(join3(root, dir));
|
|
153
258
|
} catch {
|
|
154
259
|
continue;
|
|
155
260
|
}
|
|
156
|
-
for (const name of names) files[`${dir}/${name}`] =
|
|
261
|
+
for (const name of names) files[`${dir}/${name}`] = readFileSync4(join3(root, dir, name), "utf8");
|
|
157
262
|
}
|
|
158
263
|
return files;
|
|
159
264
|
}
|
|
160
|
-
async function main(
|
|
265
|
+
async function main(argv) {
|
|
266
|
+
const guideOnly = argv.includes("--guide-only");
|
|
267
|
+
const [command, folder = "."] = argv.filter((a) => a !== "--guide-only");
|
|
161
268
|
if (command === "init") return init(resolve(folder));
|
|
269
|
+
if (command === "update") return update(resolve(folder), { guideOnly });
|
|
162
270
|
const mapper = await loadMapper();
|
|
163
271
|
const files = readWorkspace(resolve(folder));
|
|
164
272
|
if (command === "check") {
|
|
@@ -176,7 +284,14 @@ async function main([command, folder = "."]) {
|
|
|
176
284
|
process.stdout.write(mapper.v2Index(files));
|
|
177
285
|
return 0;
|
|
178
286
|
}
|
|
179
|
-
console.error(
|
|
287
|
+
console.error(
|
|
288
|
+
"usage: vds init [folder] | vds check [folder] | vds index [folder] | vds update [folder]"
|
|
289
|
+
);
|
|
180
290
|
return 2;
|
|
181
291
|
}
|
|
182
|
-
|
|
292
|
+
try {
|
|
293
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.error(e.message);
|
|
296
|
+
process.exitCode = 1;
|
|
297
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teurons/vds",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "The vds command: check, index and run a VDS workspace on your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"url": "git+https://github.com/teurons/vds.git"
|
|
20
20
|
},
|
|
21
21
|
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
"tag": "next"
|
|
22
|
+
"access": "public"
|
|
24
23
|
}
|
|
25
24
|
}
|