@teurons/vds 0.0.3 → 0.0.5

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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/vds.js +144 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,16 +34,18 @@ Set up VDS in this project.
34
34
  npx @teurons/vds init [folder] # start a workspace (never overwrites one)
35
35
  npx @teurons/vds check [folder] # print every problem; exit 1 if any
36
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
- `folder` defaults to the current folder.
40
+ `folder` defaults to the current folder, or to `vds-workspace` inside it when the current folder
41
+ is not a workspace itself. So from your project root, `vds check` checks `vds-workspace`.
40
42
 
41
43
  ## A workspace
42
44
 
43
45
  ```text
44
46
  vds-workspace/
45
47
  VDS.md how agents and people work on this spec
46
- .vds.json local settings, such as the studio port
48
+ .vds.json local settings: the port the local studio will use (coming soon)
47
49
  capabilities/<CODE>.yaml rules, each one EARS statement with an id like ONB-01
48
50
  flows/<id>.yaml versioned flows whose steps point at rule ids
49
51
  scenarios/<RULE-ID>.feature Gherkin scenarios, one file per rule
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 readFileSync2 } from "node:fs";
5
- import { join as join2, resolve } from "node:path";
4
+ import { existsSync as existsSync3, 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
- var FOLDERS = ["capabilities", "flows", "scenarios"];
101
- var FIRST_PORT = 4600;
100
+
101
+ // src/version.js
102
+ import { readFileSync as readFileSync2 } from "node:fs";
103
+ var baked = false ? null : "0.0.5";
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"), GUIDE);
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,34 +254,50 @@ function readWorkspace(root) {
149
254
  for (const dir of FOLDERS2) {
150
255
  let names = [];
151
256
  try {
152
- names = readdirSync(join2(root, dir));
257
+ names = readdirSync(join3(root, dir));
153
258
  } catch {
154
259
  continue;
155
260
  }
156
- for (const name of names) files[`${dir}/${name}`] = readFileSync2(join2(root, dir, name), "utf8");
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([command, folder = "."]) {
161
- if (command === "init") return init(resolve(folder));
265
+ function defaultFolder() {
266
+ const isWorkspace = [...FOLDERS2, "VDS.md"].some((name) => existsSync3(name));
267
+ return !isWorkspace && existsSync3("vds-workspace") ? "vds-workspace" : ".";
268
+ }
269
+ async function check(folder) {
162
270
  const mapper = await loadMapper();
163
- const files = readWorkspace(resolve(folder));
164
- if (command === "check") {
165
- const findings = mapper.v2Check(files);
166
- if ("error" in findings) throw new Error(findings.error);
167
- if (findings.length === 0) {
168
- console.log("valid");
169
- return 0;
170
- }
171
- console.error(`${findings.length} problem(s):`);
172
- for (const f of findings) console.error(`- ${f.path}: ${f.message} [${f.code}]`);
173
- return 1;
271
+ const findings = mapper.v2Check(readWorkspace(resolve(folder)));
272
+ if ("error" in findings) throw new Error(findings.error);
273
+ if (findings.length === 0) {
274
+ console.log("valid");
275
+ return 0;
174
276
  }
277
+ console.error(`${findings.length} problem(s):`);
278
+ for (const f of findings) console.error(`- ${f.path}: ${f.message} [${f.code}]`);
279
+ return 1;
280
+ }
281
+ async function main(argv) {
282
+ const guideOnly = argv.includes("--guide-only");
283
+ const [command, named] = argv.filter((a) => a !== "--guide-only");
284
+ if (command === "init") return init(resolve(named ?? "."));
285
+ const folder = named ?? defaultFolder();
286
+ if (command === "update") return update(resolve(folder), { guideOnly });
287
+ if (command === "check") return check(folder);
175
288
  if (command === "index") {
176
- process.stdout.write(mapper.v2Index(files));
289
+ const mapper = await loadMapper();
290
+ process.stdout.write(mapper.v2Index(readWorkspace(resolve(folder))));
177
291
  return 0;
178
292
  }
179
- console.error("usage: vds init [folder] | vds check [folder] | vds index [folder]");
293
+ console.error(
294
+ "usage: vds init [folder] | vds check [folder] | vds index [folder] | vds update [folder]"
295
+ );
180
296
  return 2;
181
297
  }
182
- process.exitCode = await main(process.argv.slice(2));
298
+ try {
299
+ process.exitCode = await main(process.argv.slice(2));
300
+ } catch (e) {
301
+ console.error(e.message);
302
+ process.exitCode = 1;
303
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teurons/vds",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "The vds command: check, index and run a VDS workspace on your machine.",
5
5
  "type": "module",
6
6
  "bin": {