@roster-lock/config-editor-cli 0.0.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.
Files changed (164) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE.md +11 -0
  3. package/README.md +40 -0
  4. package/dist/commands/draft/from-lock.js +31 -0
  5. package/dist/commands/draft/from-lock.js.map +1 -0
  6. package/dist/commands/draft/index.js +22 -0
  7. package/dist/commands/draft/index.js.map +1 -0
  8. package/dist/commands/draft/init.js +25 -0
  9. package/dist/commands/draft/init.js.map +1 -0
  10. package/dist/commands/draft/promote.js +24 -0
  11. package/dist/commands/draft/promote.js.map +1 -0
  12. package/dist/commands/draft/publish.js +26 -0
  13. package/dist/commands/draft/publish.js.map +1 -0
  14. package/dist/commands/draft/validate.js +30 -0
  15. package/dist/commands/draft/validate.js.map +1 -0
  16. package/dist/commands/engine/add-asset.js +44 -0
  17. package/dist/commands/engine/add-asset.js.map +1 -0
  18. package/dist/commands/engine/add-piece-type.js +47 -0
  19. package/dist/commands/engine/add-piece-type.js.map +1 -0
  20. package/dist/commands/engine/edit-definition.js +49 -0
  21. package/dist/commands/engine/edit-definition.js.map +1 -0
  22. package/dist/commands/engine/index.js +24 -0
  23. package/dist/commands/engine/index.js.map +1 -0
  24. package/dist/commands/engine/remove-asset.js +27 -0
  25. package/dist/commands/engine/remove-asset.js.map +1 -0
  26. package/dist/commands/engine/remove-piece-type.js +28 -0
  27. package/dist/commands/engine/remove-piece-type.js.map +1 -0
  28. package/dist/commands/engine/set-info.js +22 -0
  29. package/dist/commands/engine/set-info.js.map +1 -0
  30. package/dist/commands/engine/show.js +23 -0
  31. package/dist/commands/engine/show.js.map +1 -0
  32. package/dist/commands/piece-meta/index.js +19 -0
  33. package/dist/commands/piece-meta/index.js.map +1 -0
  34. package/dist/commands/piece-meta/set.js +27 -0
  35. package/dist/commands/piece-meta/set.js.map +1 -0
  36. package/dist/commands/piece-meta/show.js +15 -0
  37. package/dist/commands/piece-meta/show.js.map +1 -0
  38. package/dist/commands/roster/add-piece.js +93 -0
  39. package/dist/commands/roster/add-piece.js.map +1 -0
  40. package/dist/commands/roster/edit-piece.js +92 -0
  41. package/dist/commands/roster/edit-piece.js.map +1 -0
  42. package/dist/commands/roster/import-piece.js +83 -0
  43. package/dist/commands/roster/import-piece.js.map +1 -0
  44. package/dist/commands/roster/index.js +24 -0
  45. package/dist/commands/roster/index.js.map +1 -0
  46. package/dist/commands/roster/list.js +24 -0
  47. package/dist/commands/roster/list.js.map +1 -0
  48. package/dist/commands/roster/remove-piece.js +30 -0
  49. package/dist/commands/roster/remove-piece.js.map +1 -0
  50. package/dist/commands/roster/rescan.js +55 -0
  51. package/dist/commands/roster/rescan.js.map +1 -0
  52. package/dist/commands/roster/test-download.js +72 -0
  53. package/dist/commands/roster/test-download.js.map +1 -0
  54. package/dist/commands/selection/add-script.js +43 -0
  55. package/dist/commands/selection/add-script.js.map +1 -0
  56. package/dist/commands/selection/index.js +21 -0
  57. package/dist/commands/selection/index.js.map +1 -0
  58. package/dist/commands/selection/run-script.js +27 -0
  59. package/dist/commands/selection/run-script.js.map +1 -0
  60. package/dist/commands/selection/set.js +41 -0
  61. package/dist/commands/selection/set.js.map +1 -0
  62. package/dist/commands/selection/show.js +15 -0
  63. package/dist/commands/selection/show.js.map +1 -0
  64. package/dist/commands/show.js +15 -0
  65. package/dist/commands/show.js.map +1 -0
  66. package/dist/commands/validate.js +31 -0
  67. package/dist/commands/validate.js.map +1 -0
  68. package/dist/index.js +55 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/lib/cli-options.js +7 -0
  71. package/dist/lib/cli-options.js.map +1 -0
  72. package/dist/lib/draft-io.js +38 -0
  73. package/dist/lib/draft-io.js.map +1 -0
  74. package/dist/lib/errors.js +32 -0
  75. package/dist/lib/errors.js.map +1 -0
  76. package/dist/lib/fs-walk.js +28 -0
  77. package/dist/lib/fs-walk.js.map +1 -0
  78. package/dist/lib/json-input.js +16 -0
  79. package/dist/lib/json-input.js.map +1 -0
  80. package/dist/lib/lock-io.js +20 -0
  81. package/dist/lib/lock-io.js.map +1 -0
  82. package/dist/lib/parse.js +52 -0
  83. package/dist/lib/parse.js.map +1 -0
  84. package/dist/lib/piece-scan.js +12 -0
  85. package/dist/lib/piece-scan.js.map +1 -0
  86. package/dist/lib/schema-help.js +38 -0
  87. package/dist/lib/schema-help.js.map +1 -0
  88. package/dist/lib/schemas.js +71 -0
  89. package/dist/lib/schemas.js.map +1 -0
  90. package/dist/lib/semver-report.js +13 -0
  91. package/dist/lib/semver-report.js.map +1 -0
  92. package/dist/lib/slugify.js +13 -0
  93. package/dist/lib/slugify.js.map +1 -0
  94. package/dist/version.js +5 -0
  95. package/dist/version.js.map +1 -0
  96. package/package.json +49 -0
  97. package/scripts/bundle.js +15 -0
  98. package/scripts/generate-version.js +14 -0
  99. package/skills/rosterlock-config-editor/SKILL.md +151 -0
  100. package/src/commands/draft/from-lock.ts +34 -0
  101. package/src/commands/draft/index.ts +5 -0
  102. package/src/commands/draft/init.ts +21 -0
  103. package/src/commands/draft/promote.ts +24 -0
  104. package/src/commands/draft/publish.ts +26 -0
  105. package/src/commands/draft/validate.ts +30 -0
  106. package/src/commands/engine/add-asset.ts +46 -0
  107. package/src/commands/engine/add-piece-type.ts +56 -0
  108. package/src/commands/engine/edit-definition.ts +58 -0
  109. package/src/commands/engine/index.ts +7 -0
  110. package/src/commands/engine/remove-asset.ts +25 -0
  111. package/src/commands/engine/remove-piece-type.ts +27 -0
  112. package/src/commands/engine/set-info.ts +17 -0
  113. package/src/commands/engine/show.ts +20 -0
  114. package/src/commands/piece-meta/index.ts +2 -0
  115. package/src/commands/piece-meta/set.ts +29 -0
  116. package/src/commands/piece-meta/show.ts +12 -0
  117. package/src/commands/roster/add-piece.ts +109 -0
  118. package/src/commands/roster/edit-piece.ts +99 -0
  119. package/src/commands/roster/import-piece.ts +112 -0
  120. package/src/commands/roster/index.ts +7 -0
  121. package/src/commands/roster/list.ts +23 -0
  122. package/src/commands/roster/remove-piece.ts +28 -0
  123. package/src/commands/roster/rescan.ts +63 -0
  124. package/src/commands/roster/test-download.ts +76 -0
  125. package/src/commands/selection/add-script.ts +44 -0
  126. package/src/commands/selection/index.ts +4 -0
  127. package/src/commands/selection/run-script.ts +30 -0
  128. package/src/commands/selection/set.ts +54 -0
  129. package/src/commands/selection/show.ts +12 -0
  130. package/src/commands/show.ts +12 -0
  131. package/src/commands/validate.ts +26 -0
  132. package/src/index.ts +85 -0
  133. package/src/lib/cli-options.ts +8 -0
  134. package/src/lib/draft-io.ts +41 -0
  135. package/src/lib/errors.ts +30 -0
  136. package/src/lib/fs-walk.ts +31 -0
  137. package/src/lib/json-input.ts +21 -0
  138. package/src/lib/lock-io.ts +18 -0
  139. package/src/lib/parse.ts +42 -0
  140. package/src/lib/piece-scan.ts +19 -0
  141. package/src/lib/schema-help.ts +44 -0
  142. package/src/lib/schemas.ts +100 -0
  143. package/src/lib/semver-report.ts +11 -0
  144. package/src/lib/slugify.ts +9 -0
  145. package/test.rosterlock.draft.json +62 -0
  146. package/tests/commands/add-piece.spec.ts +92 -0
  147. package/tests/commands/draft-validate.spec.ts +85 -0
  148. package/tests/commands/edit-definition.spec.ts +94 -0
  149. package/tests/commands/edit-piece.spec.ts +153 -0
  150. package/tests/commands/from-lock.spec.ts +80 -0
  151. package/tests/commands/help-arguments.spec.ts +31 -0
  152. package/tests/commands/import-piece.spec.ts +228 -0
  153. package/tests/commands/piece-meta.spec.ts +123 -0
  154. package/tests/commands/rescan.spec.ts +137 -0
  155. package/tests/lib/draft-io.spec.ts +76 -0
  156. package/tests/lib/errors.spec.ts +48 -0
  157. package/tests/lib/fs-walk.spec.ts +35 -0
  158. package/tests/lib/json-input.spec.ts +63 -0
  159. package/tests/lib/parse.spec.ts +85 -0
  160. package/tests/lib/schema-help.spec.ts +42 -0
  161. package/tests/lib/schemas.spec.ts +82 -0
  162. package/tests/lib/slugify.spec.ts +28 -0
  163. package/tsconfig.json +19 -0
  164. package/tsconfig.test.json +10 -0
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectionOverridesSchemas = exports.pieceMetaOverridesSchema = exports.pieceOverridesSchema = exports.pieceDefinitionSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const countSchema = zod_1.z.union([
6
+ zod_1.z.number().int(),
7
+ zod_1.z.literal("*"),
8
+ zod_1.z.tuple([zod_1.z.number().int(), zod_1.z.union([zod_1.z.number().int(), zod_1.z.literal("*")])]),
9
+ ]);
10
+ const assetSchema = zod_1.z.object({
11
+ name: zod_1.z.string(),
12
+ classification: zod_1.z.enum(["logic", "media", "doc"]),
13
+ count: countSchema,
14
+ glob: zod_1.z.array(zod_1.z.string()),
15
+ }).strict();
16
+ exports.pieceDefinitionSchema = zod_1.z.object({
17
+ selectionStrategy: zod_1.z.enum(["mandatory", "personal", "shared", "on demand"]),
18
+ requires: zod_1.z.array(zod_1.z.string()),
19
+ pathVariables: zod_1.z.array(zod_1.z.string()),
20
+ assets: zod_1.z.array(assetSchema),
21
+ }).strict();
22
+ const pieceHumanInfoSchema = zod_1.z.object({
23
+ name: zod_1.z.string(),
24
+ author: zod_1.z.string(),
25
+ url: zod_1.z.string(),
26
+ image: zod_1.z.string().optional(),
27
+ }).strict();
28
+ exports.pieceOverridesSchema = zod_1.z.object({
29
+ humanInfo: pieceHumanInfoSchema.optional(),
30
+ downloadSources: zod_1.z.array(zod_1.z.string()).optional(),
31
+ pathVariables: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).optional(),
32
+ }).strict();
33
+ const untrustedScriptRefSchema = zod_1.z.object({
34
+ src: zod_1.z.string(),
35
+ method: zod_1.z.string().optional(),
36
+ }).strict();
37
+ const jsonShallowValueSchema = zod_1.z.union([
38
+ zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean(),
39
+ zod_1.z.array(zod_1.z.string()), zod_1.z.array(zod_1.z.number()), zod_1.z.array(zod_1.z.boolean()),
40
+ ]);
41
+ const jsonShallowObjectSchema = zod_1.z.record(zod_1.z.string(), jsonShallowValueSchema);
42
+ exports.pieceMetaOverridesSchema = zod_1.z.object({
43
+ schema: zod_1.z.record(zod_1.z.string(), zod_1.z.enum(["boolean", "number", "string", "boolean[]", "number[]", "string[]"])).optional(),
44
+ defaultMeta: jsonShallowObjectSchema.optional(),
45
+ values: zod_1.z.record(zod_1.z.string(), jsonShallowObjectSchema).optional(),
46
+ }).strict();
47
+ const selectedPieceSchema = zod_1.z.lazy(() => zod_1.z.object({
48
+ id: zod_1.z.string(),
49
+ required: zod_1.z.record(zod_1.z.string(), zod_1.z.object({
50
+ mandatory: zod_1.z.array(selectedPieceSchema),
51
+ selectable: zod_1.z.array(selectedPieceSchema),
52
+ }).strict()),
53
+ }).strict());
54
+ const userSelectionValidationSchema = zod_1.z.object({
55
+ count: countSchema,
56
+ unique: zod_1.z.boolean(),
57
+ banList: zod_1.z.array(zod_1.z.string()),
58
+ customValidation: zod_1.z.array(untrustedScriptRefSchema),
59
+ }).strict();
60
+ exports.selectionOverridesSchemas = {
61
+ normal: zod_1.z.object({
62
+ validation: userSelectionValidationSchema.optional(),
63
+ mergeAlgorithm: untrustedScriptRefSchema.optional(),
64
+ }).strict(),
65
+ preselected: zod_1.z.object({
66
+ pieces: zod_1.z.array(selectedPieceSchema).optional(),
67
+ }).strict(),
68
+ unselectable: zod_1.z.object({}).strict(),
69
+ "game-controlled": zod_1.z.object({}).strict(),
70
+ };
71
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/lib/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printSemverReasons = printSemverReasons;
4
+ function printSemverReasons(semver) {
5
+ const reasons = semver.getReasons();
6
+ for (const level of ["major", "minor", "patch"]) {
7
+ for (const { reason, info } of reasons[level]) {
8
+ console.log(`[${level}] ${reason} ${JSON.stringify(info)}`);
9
+ }
10
+ }
11
+ console.log(`Version: ${semver.toString()}`);
12
+ }
13
+ //# sourceMappingURL=semver-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver-report.js","sourceRoot":"","sources":["../../src/lib/semver-report.ts"],"names":[],"mappings":";;;AAEA,4BAAmC,MAAc;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IACpC,KAAI,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAU,EAAC,CAAC;QACvD,KAAI,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,KAAK,CAAC,EAAC,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.slugify = slugify;
4
+ function slugify(text) {
5
+ return text
6
+ .toLowerCase()
7
+ .trim()
8
+ .replace(/[\s_]+/g, '-')
9
+ .replace(/[^a-z0-9-]+/g, '')
10
+ .replace(/^-+|-+$/g, '')
11
+ .replace(/-+/g, '-');
12
+ }
13
+ //# sourceMappingURL=slugify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.js","sourceRoot":"","sources":["../../src/lib/slugify.ts"],"names":[],"mappings":";;;AAAA,iBAAwB,IAAY;IAClC,OAAO,IAAI;SACR,WAAW,EAAE;SACb,IAAI,EAAE;SACN,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ exports.VERSION = "0.0.1";
5
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":";;;AACa,QAAA,OAAO,GAAG,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@roster-lock/config-editor-cli",
3
+ "version": "0.0.1",
4
+ "description": "A cli tool to build roster lock configs",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/formula1/roster-lock.git",
8
+ "directory": "core/config-editor-cli"
9
+ },
10
+ "license": "JSON",
11
+ "author": "Sam Tobia",
12
+ "type": "commonjs",
13
+ "main": "dist/index.js",
14
+ "bin": {
15
+ "rosterlock-config-editor": "dist/index.js"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "dependencies": {
21
+ "ajv": "^8.17.1",
22
+ "commander": "^14.0.3",
23
+ "zod": "^4.4.3",
24
+ "@roster-lock/plugin-runtime": "0.1.0",
25
+ "@roster-lock/shared": "0.1.0",
26
+ "@roster-lock/types": "1.0.0",
27
+ "@roster-lock/utils": "1.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.14.1",
31
+ "@yao-pkg/pkg": "^6.19.0",
32
+ "esbuild": "^0.28.1",
33
+ "rimraf": "^6.1.3",
34
+ "tsx": "^4.0.0",
35
+ "typescript": "^7.0.2",
36
+ "vitest": "^4.1.5"
37
+ },
38
+ "scripts": {
39
+ "prebuild": "node scripts/generate-version.js",
40
+ "build": "rimraf ./dist && tsc",
41
+ "predev": "node scripts/generate-version.js",
42
+ "dev": "tsx src/index.ts",
43
+ "start": "node dist/index.js",
44
+ "test": "vitest run tests/",
45
+ "prebundle": "pnpm run build",
46
+ "bundle": "node scripts/bundle.js",
47
+ "package": "pnpm run bundle && pkg pkg-build/bundle.cjs -t node24-linux-x64 -o bin/rosterlock-config-editor --no-signature"
48
+ }
49
+ }
@@ -0,0 +1,15 @@
1
+ // Bundles the compiled dist/index.js (and its workspace/node_modules deps) into a
2
+ // single CJS file, so `pkg` can package it into a standalone binary without needing
3
+ // to resolve pnpm's symlinked node_modules layout itself.
4
+ const esbuild = require("esbuild");
5
+ const { join } = require("node:path");
6
+
7
+ esbuild.buildSync({
8
+ entryPoints: [join(__dirname, "..", "dist", "index.js")],
9
+ bundle: true,
10
+ platform: "node",
11
+ format: "cjs",
12
+ outfile: join(__dirname, "..", "pkg-build", "bundle.cjs"),
13
+ });
14
+
15
+ console.log("Bundled to pkg-build/bundle.cjs");
@@ -0,0 +1,14 @@
1
+ // Bakes package.json's version into a source file at build time, so the
2
+ // version is embedded directly in the compiled/bundled output instead of
3
+ // being read from package.json at runtime (which wouldn't be available if
4
+ // this package is later packaged into a standalone binary, e.g. via pkg).
5
+ const { writeFileSync, mkdirSync } = require("node:fs");
6
+ const { join } = require("node:path");
7
+
8
+ const pkg = require("../package.json");
9
+
10
+ mkdirSync(join(__dirname, "..", "src"), { recursive: true });
11
+ writeFileSync(
12
+ join(__dirname, "..", "src", "version.ts"),
13
+ `// Generated by scripts/generate-version.js — do not edit by hand.\nexport const VERSION = ${JSON.stringify(pkg.version)};\n`
14
+ );
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: rosterlock-config-editor
3
+ description: Create and edit roster-lock config drafts (engine piece types, roster pieces, selection config) using the rosterlock CLI, and validate/promote/publish them. Use whenever asked to build, edit, inspect, or validate a *.rosterlock.draft.json or *.rosterlock.json file.
4
+ argument-hint: e.g. "create a draft for a new game called Foo" or "add a character piece type with a sprite asset"
5
+ ---
6
+
7
+ # rosterlock CLI
8
+
9
+ `rosterlock` is the CLI for building and editing roster-lock config files: the
10
+ engine's piece-type definitions, the roster of actual pieces per type, and the
11
+ per-type selection config. This skill ships inside the `@roster-lock/config-editor-cli`
12
+ package itself (this file lives at `skills/rosterlock-config-editor/SKILL.md`
13
+ relative to that package's root, `core/config-editor-cli/` in the roster-lock
14
+ monorepo) so it travels with the CLI - installing or cloning just this package is
15
+ enough to have it.
16
+
17
+ It edits a **draft** file (`*.rosterlock.draft.json`), which wraps a `stagedLock`
18
+ (the config being built), a `previousLock` (the last published baseline, for
19
+ semver diffing), and draft-only bookkeeping (`draft.rosterPieceInfo`,
20
+ `draft.selectionScriptInfo`).
21
+
22
+ **`--help` is the source of truth for exact flags.** This doc gives you the shape
23
+ of the CLI and the order operations need to happen in; run
24
+ `$ROSTERLOCK <command> --help` before using a command you haven't used yet in this
25
+ session, and re-check it if a command errors on unrecognized/missing flags - flags
26
+ do change as the CLI evolves and this doc can drift.
27
+
28
+ ## Getting a runnable binary
29
+
30
+ From this package's root, build a standalone binary (bundled via esbuild, packaged
31
+ via `@yao-pkg/pkg` - runs without `pnpm`/`node_modules` on `PATH`):
32
+
33
+ ```bash
34
+ pnpm run package
35
+ ```
36
+
37
+ This produces `bin/rosterlock-config-editor` (gitignored, built on demand - targets
38
+ `node24-linux-x64` by default; edit the `package` script in `package.json` to add
39
+ other targets like `node24-macos-arm64`/`node24-win-x64` if needed). Check whether
40
+ it already exists before rebuilding - it only needs to be regenerated after source
41
+ changes:
42
+
43
+ ```bash
44
+ test -x bin/rosterlock-config-editor || pnpm run package
45
+ ```
46
+
47
+ If you only need to run something once and don't want to package a binary, and
48
+ you're working inside the roster-lock monorepo (this package's dependencies are
49
+ workspace-linked there), `pnpm dev -- <args>` runs the CLI directly from source
50
+ via `tsx` (no build/package step, slightly slower startup).
51
+
52
+ All examples below assume `ROSTERLOCK=./bin/rosterlock-config-editor` (adjust the
53
+ path if you're invoking it from elsewhere - e.g. `pnpm dev --` if running from
54
+ source).
55
+
56
+ ## Command map
57
+
58
+ ```
59
+ $ROSTERLOCK draft init [path] --title <t> --author <a>
60
+ $ROSTERLOCK draft from-lock <lockPath> [draftPath] [--clear-rosters]
61
+ $ROSTERLOCK draft validate [-d <draft>] # strict: every issue blocking publish/promote, at once
62
+ $ROSTERLOCK draft promote [-d <draft>] # stagedLock becomes the new previousLock baseline
63
+ $ROSTERLOCK draft publish <outLockPath> [-d <draft>] # writes a standalone versioned lock file
64
+
65
+ $ROSTERLOCK validate [-d <draft>] [--lock <path>] # looser structural check (drafts are allowed to be WIP)
66
+ $ROSTERLOCK show [-d <draft>] # print stagedLock as JSON
67
+
68
+ $ROSTERLOCK engine set-info --name <n> --engine-version <v> [-d <draft>]
69
+ $ROSTERLOCK engine add-piece-type <pieceType> --strategy <s> [--requires <t,..>] [--path-variables <n,..>] [--json <file|->]
70
+ $ROSTERLOCK engine edit-definition <pieceType> [--strategy <s>] [--requires <t,..>] [--path-variables <n,..>] [--json <file|->]
71
+ $ROSTERLOCK engine add-asset <pieceType> <assetName> --classification <c> --count <n> --glob <pattern> [-d <draft>]
72
+ $ROSTERLOCK engine remove-asset <pieceType> <assetName> [-d <draft>]
73
+ $ROSTERLOCK engine remove-piece-type <pieceType> [-d <draft>] # refuses if the roster still has pieces
74
+ $ROSTERLOCK engine show [pieceType] [-d <draft>]
75
+
76
+ $ROSTERLOCK roster add-piece <pieceType> <folder> [--path-variables <k=v,..>] [--download-source <url>] [--json <file|->]
77
+ $ROSTERLOCK roster edit-piece <pieceType> <pieceId> [--name/--author/--url/--image <v>] [--add-download-source <url>] [--remove-download-source <url>] [--path-variables <k=v,..>] [--remove-path-variable <name>] [--json <file|->]
78
+ $ROSTERLOCK roster rescan <pieceType> <pieceId> [folder] [--path-variables <k=v,..>] # recompute hashes in place after asset/file changes; keeps id, humanInfo, downloadSources, requiredPieces
79
+ $ROSTERLOCK roster test-download <pieceType> <pieceId> <sourceUrl> # downloads and verifies a source's hash matches
80
+ $ROSTERLOCK roster remove-piece <pieceType> <pieceId>
81
+ $ROSTERLOCK roster list [pieceType] [-d <draft>]
82
+
83
+ $ROSTERLOCK selection add-script <fileOrFolder> [--key <relPath>]
84
+ $ROSTERLOCK selection set <pieceType> --type <normal|preselected|unselectable|game-controlled> [--json <file|->]
85
+ $ROSTERLOCK selection run-script <scriptRef>
86
+ $ROSTERLOCK selection show [-d <draft>]
87
+ ```
88
+
89
+ The `-d, --draft <path>` option (present on almost every command) defaults to the
90
+ sole `*.rosterlock.draft.json` file in the current directory - only pass it
91
+ explicitly when there's more than one draft in play, or you're not running from
92
+ the draft's directory.
93
+
94
+ ## Typical workflow
95
+
96
+ 1. **Start a draft.** Either `draft init` (empty) or `draft from-lock <existing.rosterlock.json>`
97
+ (start from a published lock; add `--clear-rosters` to keep the engine config
98
+ but start with empty rosters/selection - useful for "same game, new content
99
+ season").
100
+ 2. **Define the engine** (piece types): `engine add-piece-type`, then
101
+ `engine add-asset` for each asset the piece type needs. `engine edit-definition`
102
+ changes strategy/requires/path-variables later without re-adding assets.
103
+ 3. **Populate rosters** (actual pieces): `roster add-piece <type> <folder>` scans a
104
+ folder against the piece type's asset definitions and computes content hashes.
105
+ It needs at least one download source, from `--download-source`, a
106
+ `rosterlock.piece-meta.json` in the folder, or `--json`. Use `roster edit-piece`
107
+ to fix up humanInfo/sources/path-variables afterward. Its id is derived from
108
+ `humanInfo` (`@author/name`, or `#logicHash/mediaHash` if unnamed) - there's no
109
+ flag to set it directly. To update an **existing** piece's files (e.g. after
110
+ adding a new asset type or editing media) without changing its id or losing its
111
+ `requiredPieces`, use `roster rescan <type> <pieceId> [folder]` instead of
112
+ removing and re-adding it - `add-piece` always resets `requiredPieces` to empty
113
+ stubs, which `roster edit-piece` cannot restore.
114
+ 4. **Configure selection** per piece type: `selection set <type> --type <kind>`.
115
+ `normal` needs `validation` (count/unique/banList/customValidation); `preselected`
116
+ needs `pieces`; `unselectable`/`game-controlled` need neither - just `pieceMeta`
117
+ if you're using per-piece metadata. Pass the type-specific fields via `--json`
118
+ (see shape in `selection set --help`).
119
+ 5. **Validate before finishing up**: `draft validate` runs the full (non-draft)
120
+ schema against `stagedLock` and reports *every* issue in one pass (not just the
121
+ first) - use it to iterate through problems instead of running `publish`
122
+ repeatedly. (Plain `validate` without `draft` only checks structure - it
123
+ deliberately allows an in-progress/incomplete draft.)
124
+ 6. **Promote and/or publish**: `draft promote` moves `stagedLock` forward as the
125
+ new `previousLock` baseline (for future diffs) without producing an output file.
126
+ `draft publish <path>` writes a standalone, semver-bumped lock file and prints
127
+ why the version changed, without touching the draft.
128
+
129
+ ## `--json` inputs
130
+
131
+ Several commands accept `--json <file>` to supply structured input too large/awkward
132
+ for flags (full piece-type definitions, type-specific selection fields, bulk piece
133
+ edits). Pass `-` instead of a file path to pipe JSON via stdin instead of writing a
134
+ temp file - handy when generating the JSON programmatically:
135
+
136
+ ```bash
137
+ echo '{"selectionStrategy":"shared","requires":[],"pathVariables":[],"assets":[]}' \
138
+ | $ROSTERLOCK engine edit-definition character --json -
139
+ ```
140
+
141
+ Every `--json` input is validated with zod before use; the command's `--help`
142
+ prints the exact expected shape (e.g. `Shape: { selectionStrategy: "mandatory" |
143
+ ... }`). A shape mismatch fails fast with a per-field error - fix the JSON and
144
+ retry rather than guessing.
145
+
146
+ ## Error output
147
+
148
+ Validation errors (from `--json` shape checks, or from `draft validate`/`publish`/
149
+ `promote` against the full lock schema) print one issue per line as
150
+ `<path> <message>`, and the process exits non-zero. Multiple issues are reported
151
+ together, not just the first - read the whole list before making changes.
@@ -0,0 +1,34 @@
1
+ import { Command } from "commander";
2
+ import { resolve } from "node:path";
3
+ import { EMPTY_ROSTER_DRAFT } from "@roster-lock/shared";
4
+ import { cloneJSON } from "@roster-lock/utils";
5
+ import { readLock } from "../../lib/lock-io";
6
+ import { defaultDraftPath, writeDraft } from "../../lib/draft-io";
7
+ import { withErrorHandling } from "../../lib/errors";
8
+
9
+ export const fromLockCommand = new Command("from-lock")
10
+ .description("Create a new draft from an existing published lock file")
11
+ .argument("<lockPath>", "path to the published lock file")
12
+ .argument("[draftPath]", "where to write the draft (defaults to a name derived from the lock's title)")
13
+ .option(
14
+ "--clear-rosters",
15
+ "keep the lock's engine config, but start with empty rosters and no per-piece-type selection config"
16
+ )
17
+ .action(withErrorHandling(async (lockPath: string, draftPath: string | undefined, opts: { clearRosters?: boolean }) => {
18
+ const lock = readLock(lockPath);
19
+
20
+ const draft = cloneJSON(EMPTY_ROSTER_DRAFT);
21
+ draft.previousLock = cloneJSON(lock);
22
+ draft.stagedLock = cloneJSON(lock);
23
+
24
+ if(opts.clearRosters){
25
+ for(const pieceType of Object.keys(draft.stagedLock.rosters)){
26
+ draft.stagedLock.rosters[pieceType] = [];
27
+ }
28
+ draft.stagedLock.selection.piece = {};
29
+ }
30
+
31
+ const path = draftPath ? resolve(process.cwd(), draftPath) : defaultDraftPath(lock.title);
32
+ writeDraft(path, draft);
33
+ console.log(`Created draft: ${path}${opts.clearRosters ? " (rosters cleared)" : ""}`);
34
+ }));
@@ -0,0 +1,5 @@
1
+ export * from "./init";
2
+ export * from "./from-lock";
3
+ export * from "./promote";
4
+ export * from "./publish";
5
+ export * from "./validate";
@@ -0,0 +1,21 @@
1
+ import { Command } from "commander";
2
+ import { resolve } from "node:path";
3
+ import { EMPTY_ROSTER_DRAFT } from "@roster-lock/shared";
4
+ import { cloneJSON } from "@roster-lock/utils";
5
+ import { defaultDraftPath, writeDraft } from "../../lib/draft-io";
6
+ import { withErrorHandling } from "../../lib/errors";
7
+
8
+ export const initCommand = new Command("init")
9
+ .description("Create a new empty roster-lock draft")
10
+ .argument("[draftPath]", "where to write the draft (defaults to a name derived from --title)")
11
+ .option("--title <title>", "title to seed the staged lock with")
12
+ .option("--author <author>", "author to seed the staged lock with")
13
+ .action(withErrorHandling(async (draftPath: string | undefined, opts: { title?: string, author?: string }) => {
14
+ const draft = cloneJSON(EMPTY_ROSTER_DRAFT);
15
+ if(opts.title) draft.stagedLock.title = opts.title;
16
+ if(opts.author) draft.stagedLock.author = opts.author;
17
+
18
+ const path = draftPath ? resolve(process.cwd(), draftPath) : defaultDraftPath(opts.title);
19
+ writeDraft(path, draft);
20
+ console.log(`Created draft: ${path}`);
21
+ }));
@@ -0,0 +1,24 @@
1
+ import { Command } from "commander";
2
+ import { ROSTERLOCK_V1_CASTER_JSONSCHEMA, diffLocks } from "@roster-lock/shared";
3
+ import { cloneJSON } from "@roster-lock/utils";
4
+ import { resolveDraftPath, readDraft, writeDraft } from "../../lib/draft-io";
5
+ import { withDraftOption } from "../../lib/cli-options";
6
+ import { withErrorHandling } from "../../lib/errors";
7
+ import { printSemverReasons } from "../../lib/semver-report";
8
+
9
+ export const promoteCommand = withDraftOption(new Command("promote"))
10
+ .description("Move the staged lock forward as the new baseline (previousLock) for future diffs")
11
+ .action(withErrorHandling(async (opts: { draft?: string }) => {
12
+ const draftPath = resolveDraftPath(opts.draft);
13
+ const draft = readDraft(draftPath);
14
+
15
+ const updatedDraft = cloneJSON(draft);
16
+ ROSTERLOCK_V1_CASTER_JSONSCHEMA.cast(updatedDraft.stagedLock);
17
+
18
+ const semver = diffLocks(updatedDraft.previousLock, updatedDraft.stagedLock);
19
+ updatedDraft.stagedLock.version = semver.toString();
20
+ updatedDraft.previousLock = cloneJSON(updatedDraft.stagedLock);
21
+
22
+ writeDraft(draftPath, updatedDraft);
23
+ printSemverReasons(semver);
24
+ }));
@@ -0,0 +1,26 @@
1
+ import { Command } from "commander";
2
+ import { ROSTERLOCK_V1_CASTER_JSONSCHEMA, diffLocks } from "@roster-lock/shared";
3
+ import { cloneJSON } from "@roster-lock/utils";
4
+ import { resolveDraftPath, readDraft } from "../../lib/draft-io";
5
+ import { writeLock } from "../../lib/lock-io";
6
+ import { withDraftOption } from "../../lib/cli-options";
7
+ import { withErrorHandling } from "../../lib/errors";
8
+ import { printSemverReasons } from "../../lib/semver-report";
9
+
10
+ export const publishCommand = withDraftOption(new Command("publish"))
11
+ .description("Publish the staged lock as a standalone, versioned lock file (does not modify the draft)")
12
+ .argument("<outLockPath>", "where to write the published lock file")
13
+ .action(withErrorHandling(async (outLockPath: string, opts: { draft?: string }) => {
14
+ const draftPath = resolveDraftPath(opts.draft);
15
+ const draft = readDraft(draftPath);
16
+
17
+ const stagedLock = cloneJSON(draft.stagedLock);
18
+ ROSTERLOCK_V1_CASTER_JSONSCHEMA.cast(stagedLock);
19
+
20
+ const semver = diffLocks(draft.previousLock, stagedLock);
21
+ stagedLock.version = semver.toString();
22
+
23
+ writeLock(outLockPath, stagedLock);
24
+ printSemverReasons(semver);
25
+ console.log(`Published: ${outLockPath}`);
26
+ }));
@@ -0,0 +1,30 @@
1
+ import { Command } from "commander";
2
+ import { ROSTERLOCK_V1_CASTER_JSONSCHEMA } from "@roster-lock/shared";
3
+ import { resolveDraftPath, readDraft } from "../../lib/draft-io";
4
+ import { withDraftOption } from "../../lib/cli-options";
5
+ import { formatError } from "../../lib/errors";
6
+
7
+ export const draftValidateCommand = withDraftOption(new Command("validate"))
8
+ .description(
9
+ "Report every issue blocking \"draft publish\"/\"draft promote\": runs the staged lock through the full " +
10
+ "(non-draft) schema and lists all failures at once, without writing anything"
11
+ )
12
+ .action(async (opts: { draft?: string }) => {
13
+ try {
14
+ const draftPath = resolveDraftPath(opts.draft);
15
+ const draft = readDraft(draftPath);
16
+
17
+ const result = ROSTERLOCK_V1_CASTER_JSONSCHEMA.safeCast(draft.stagedLock);
18
+ if(result.valid){
19
+ console.log("OK: staged lock is valid and ready to publish");
20
+ return;
21
+ }
22
+
23
+ console.error(`${result.error.length} issue(s) found in staged lock:`);
24
+ console.error(formatError(result.error));
25
+ process.exitCode = 1;
26
+ } catch(e){
27
+ console.error(formatError(e));
28
+ process.exitCode = 1;
29
+ }
30
+ });
@@ -0,0 +1,46 @@
1
+ import { Command } from "commander";
2
+ import { ROSTERLOCK_V1_CASTER_JSONSCHEMA, validatePieceDefinition } from "@roster-lock/shared";
3
+ import { EngineAssetDefinition, AssetClassification } from "@roster-lock/types";
4
+ import { resolveDraftPath, readDraft, writeDraft } from "../../lib/draft-io";
5
+ import { withDraftOption } from "../../lib/cli-options";
6
+ import { withErrorHandling } from "../../lib/errors";
7
+ import { parseCount } from "../../lib/parse";
8
+
9
+ function collectGlob(value: string, previous: Array<string>){
10
+ previous.push(value);
11
+ return previous;
12
+ }
13
+
14
+ export const addAssetCommand = withDraftOption(new Command("add-asset"))
15
+ .description("Add an asset definition to an existing piece type")
16
+ .argument("<pieceType>", "the piece type key")
17
+ .argument("<assetName>", "the asset's name")
18
+ .requiredOption("--classification <classification>", "logic|media|doc")
19
+ .requiredOption("--count <count>", "exact number, \"*\", or \"min:max\" (max may be \"*\")")
20
+ .option("--glob <pattern>", "glob pattern (repeatable)", collectGlob, [] as Array<string>)
21
+ .action(withErrorHandling(async (pieceType: string, assetName: string, opts: {
22
+ draft?: string, classification: string, count: string, glob: Array<string>
23
+ }) => {
24
+ const draftPath = resolveDraftPath(opts.draft);
25
+ const draft = readDraft(draftPath);
26
+
27
+ const definition = draft.stagedLock.engine.pieceDefinitions[pieceType];
28
+ if(!definition) throw new Error(`Unknown piece type "${pieceType}"`);
29
+ if(opts.glob.length === 0) throw new Error("At least one --glob is required");
30
+ if(!["logic", "media", "doc"].includes(opts.classification)){
31
+ throw new Error(`Invalid --classification "${opts.classification}" (expected logic|media|doc)`);
32
+ }
33
+
34
+ const asset: EngineAssetDefinition = {
35
+ name: assetName,
36
+ classification: opts.classification as AssetClassification,
37
+ count: parseCount(opts.count),
38
+ glob: opts.glob,
39
+ };
40
+ definition.assets.push(asset);
41
+ validatePieceDefinition(pieceType, definition, draft.stagedLock.engine);
42
+ ROSTERLOCK_V1_CASTER_JSONSCHEMA.cast(draft.stagedLock);
43
+
44
+ writeDraft(draftPath, draft);
45
+ console.log(`Added asset "${assetName}" to piece type "${pieceType}"`);
46
+ }));
@@ -0,0 +1,56 @@
1
+ import { Command } from "commander";
2
+ import { ROSTERLOCK_V1_CASTER_JSONSCHEMA, validatePieceDefinition } from "@roster-lock/shared";
3
+ import { RosterLockV1Config } from "@roster-lock/types";
4
+ import { resolveDraftPath, readDraft, writeDraft } from "../../lib/draft-io";
5
+ import { withDraftOption } from "../../lib/cli-options";
6
+ import { withErrorHandling } from "../../lib/errors";
7
+ import { parseCommaList, parseStrategy } from "../../lib/parse";
8
+ import { readJsonInput } from "../../lib/json-input";
9
+ import { pieceDefinitionSchema } from "../../lib/schemas";
10
+ import { describeSchemaShape } from "../../lib/schema-help";
11
+
12
+ type PieceDefinition = RosterLockV1Config["engine"]["pieceDefinitions"][string];
13
+
14
+ export const addPieceTypeCommand = withDraftOption(new Command("add-piece-type"))
15
+ .description("Define a new piece type in the staged lock's engine")
16
+ .argument("<pieceType>", "the piece type key")
17
+ .option("--strategy <strategy>", "mandatory|personal|shared|on-demand")
18
+ .option("--requires <types>", "comma-separated list of required on-demand piece types")
19
+ .option("--path-variables <names>", "comma-separated list of path variable names")
20
+ .option(
21
+ "--json <file>",
22
+ "read the full piece-type definition (including assets) from a JSON file (or \"-\" for stdin), instead of the flags above. " +
23
+ `Shape: ${describeSchemaShape(pieceDefinitionSchema)}`
24
+ )
25
+ .action(withErrorHandling(async (pieceType: string, opts: {
26
+ draft?: string, strategy?: string, requires?: string, pathVariables?: string, json?: string
27
+ }) => {
28
+ const draftPath = resolveDraftPath(opts.draft);
29
+ const draft = readDraft(draftPath);
30
+
31
+ if(draft.stagedLock.engine.pieceDefinitions[pieceType]){
32
+ throw new Error(`Piece type "${pieceType}" already exists; use "engine remove-piece-type" first`);
33
+ }
34
+
35
+ const definition: PieceDefinition = opts.json
36
+ ? await readJsonInput(opts.json, pieceDefinitionSchema)
37
+ : {
38
+ selectionStrategy: parseStrategy(requireOption(opts.strategy, "--strategy")),
39
+ requires: parseCommaList(opts.requires),
40
+ pathVariables: parseCommaList(opts.pathVariables),
41
+ assets: [],
42
+ };
43
+
44
+ draft.stagedLock.engine.pieceDefinitions[pieceType] = definition;
45
+ draft.stagedLock.rosters[pieceType] ??= [];
46
+ validatePieceDefinition(pieceType, definition, draft.stagedLock.engine);
47
+ ROSTERLOCK_V1_CASTER_JSONSCHEMA.cast(draft.stagedLock);
48
+
49
+ writeDraft(draftPath, draft);
50
+ console.log(`Added piece type: ${pieceType}`);
51
+ }));
52
+
53
+ function requireOption(value: string | undefined, flag: string): string {
54
+ if(value === undefined) throw new Error(`${flag} is required unless --json is given`);
55
+ return value;
56
+ }