@patterkit/cli 1.0.0 → 1.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.
Files changed (2) hide show
  1. package/dist/cli.js +102 -7
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -59,6 +59,12 @@ import {
59
59
  applySuggestionDecisions,
60
60
  qualifierNamer,
61
61
  scriptFontLicences,
62
+ planPlaceholderSpeech,
63
+ runPlaceholderSpeech,
64
+ resolveKeyFile,
65
+ readGoogleKey,
66
+ tokenSource,
67
+ describeSpeechError,
62
68
  findProjectFile,
63
69
  readProjectManifest,
64
70
  detectMergeType
@@ -70,7 +76,7 @@ import { runMergetool } from "@wildwinter/toolkit/vcs";
70
76
  // package.json
71
77
  var package_default = {
72
78
  name: "@patterkit/cli",
73
- version: "1.0.0",
79
+ version: "1.1.0",
74
80
  description: "The `patter` CLI: validate, format, export, play, test coverage, localise, and merge Patter projects.",
75
81
  keywords: [
76
82
  "patter",
@@ -112,10 +118,10 @@ var package_default = {
112
118
  "build:standalone:others": "node scripts/build-standalone.mjs --targets=linux-x64,linux-arm64,windows-x64"
113
119
  },
114
120
  dependencies: {
115
- "@patterkit/core": "1.0.0",
116
- "@patterkit/ops": "1.0.0",
121
+ "@patterkit/core": "1.1.0",
122
+ "@patterkit/ops": "1.1.0",
117
123
  "@wildwinter/simple-vc-lib": "^0.5.2",
118
- "@wildwinter/toolkit": "^1.0.0"
124
+ "@wildwinter/toolkit": "^1.2.0"
119
125
  }
120
126
  };
121
127
 
@@ -192,6 +198,10 @@ Usage:
192
198
  [--ids] IDs-only build: ship no strings; the game localises from beat IDs
193
199
  [--source-debug] IDs-only but embed the source language for debug playback (not shippable)
194
200
  [--allow-invalid] Build even when validate finds errors in the content (not shippable)
201
+ [--audio-manifest-out file] Write the audio manifest there instead of beside the audio
202
+ (a shipping manifest; its files stay relative to the audio root)
203
+ [--no-placeholder-speech] Leave Placeholder Speech out of that manifest (needs
204
+ --audio-manifest-out, so the development one keeps it)
195
205
  patter export-html [path] [-o file] A single self-contained, playable .html (runtime + story inlined;
196
206
  send it to anyone, opens in any browser; default: beside the bundle; -o - for stdout)
197
207
  patter play [path] Play a project through Patterplay and print the transcript
@@ -216,6 +226,11 @@ Usage:
216
226
  beside the bundle, as .pdf. The PDF embeds its fonts and covers any script or emoji.
217
227
  patter voice-export [path] -o file.xlsx The voice (VO) recording script
218
228
  [--all] Include every voiced line (else only "ready to record")
229
+ patter placeholder-speech [path] Make Placeholder Speech: stand-in speech for spoken lines, from Google,
230
+ into the project's placeholder status folder (needs a key: the project's key file, else
231
+ GOOGLE_APPLICATION_CREDENTIALS)
232
+ [--scene id|name] [--dry-run] [--force] (--force remakes every line, even unchanged)
233
+ [--soft] No key is a note and exit 0, not a failure (for shared CI)
219
234
  patter export-editable [path] -o file.docx An editable script to send to an editor outside Patter
220
235
  [--scene name]... (repeatable; default the whole project) [--recipient "Sam"]
221
236
  [--status] [--cast] [--all-notes] [--by name] (writes a handoffs/<id>.json record too)
@@ -247,7 +262,7 @@ var FLAGS = {
247
262
  init: { boolean: [], valued: ["name", "vcs", "bundle"], positionals: 1 },
248
263
  validate: { boolean: [], valued: [], positionals: 1 },
249
264
  format: { boolean: ["check"], valued: [], positionals: Infinity },
250
- export: { boolean: ["ids", "source-debug", "allow-invalid"], valued: ["o"], positionals: 1 },
265
+ export: { boolean: ["ids", "source-debug", "allow-invalid", "no-placeholder-speech"], valued: ["o", "audio-manifest-out"], positionals: 1 },
251
266
  "export-html": { boolean: [], valued: ["o"], positionals: 1 },
252
267
  play: { boolean: [], valued: ["scene", "block", "choices", "seed"], positionals: 1 },
253
268
  coverage: { boolean: ["json", "fail-on-gap", "propose"], valued: ["runs", "max-steps", "seed", "scene", "block", "order"], positionals: 1 },
@@ -258,6 +273,7 @@ var FLAGS = {
258
273
  "loc-import": { boolean: ["force-locale"], valued: ["locale"], positionals: 2 },
259
274
  "voice-export": { boolean: ["all"], valued: ["o"], positionals: 1 },
260
275
  "export-script": { boolean: [], valued: ["o"], positionals: 1 },
276
+ "placeholder-speech": { boolean: ["dry-run", "force", "soft"], valued: ["scene"], positionals: 1 },
261
277
  "export-editable": { boolean: ["status", "cast", "all-notes"], valued: ["o", "recipient", "by"], repeatable: ["scene"], positionals: 1 },
262
278
  "import-editable": { boolean: ["dry-run", "direct", "strict-quotes"], valued: ["as", "by"], positionals: 2 },
263
279
  suggestions: { boolean: ["accept-clean", "json"], valued: ["handoff"], positionals: 1 },
@@ -273,7 +289,7 @@ var SHORT = {
273
289
  init: "init [dir] [--name X] [--vcs git|perforce|plastic|svn] [--bundle commit|ignore]",
274
290
  validate: "validate [path]",
275
291
  format: "format <paths...> [--check]",
276
- export: "export [path] [-o FILE] [--ids | --source-debug] [--allow-invalid]",
292
+ export: "export [path] [-o FILE] [--ids | --source-debug] [--allow-invalid] [--audio-manifest-out FILE [--no-placeholder-speech]]",
277
293
  "export-html": "export-html [path] [-o FILE]",
278
294
  play: "play [path] [--scene id] [--block id] [--choices a,b,c] [--seed N]",
279
295
  coverage: "coverage [path] [--runs N] [--max-steps M] [--seed S] [--scene id] [--block id] [--order least|script] [--json] [--fail-on-gap] [--propose]",
@@ -284,6 +300,7 @@ var SHORT = {
284
300
  "loc-import": "loc-import <file> [path] [--locale xx] [--force-locale]",
285
301
  "voice-export": "voice-export [path] -o FILE.xlsx [--all]",
286
302
  "export-script": "export-script [path] [-o FILE.pdf|FILE.docx]",
303
+ "placeholder-speech": "placeholder-speech [path] [--scene id|name] [--dry-run] [--force] [--soft]",
287
304
  "export-editable": "export-editable [path] -o FILE.docx [--scene name]... [--recipient X] [--status] [--cast] [--all-notes] [--by name]",
288
305
  "import-editable": "import-editable <file.docx> [path] [--dry-run] [--direct] [--as name] [--by name] [--strict-quotes]",
289
306
  suggestions: "suggestions [path] [--handoff H-XXXX] [--accept-clean | --json]",
@@ -542,7 +559,13 @@ ${count} issue(s)${advisory}`);
542
559
  process.stdout.write(canonicalStringify(runExport(loaded, { allowInvalid }), { trailingComma: false }));
543
560
  return 0;
544
561
  }
545
- const plan = planBuild(loaded, { allowInvalid, ...typeof flags.o === "string" ? { bundlePath: flags.o } : {} });
562
+ if (flags["no-placeholder-speech"] === true && typeof flags["audio-manifest-out"] !== "string") return usage("export", "--no-placeholder-speech needs --audio-manifest-out, so the development manifest keeps its placeholder speech");
563
+ const plan = planBuild(loaded, {
564
+ allowInvalid,
565
+ ...typeof flags.o === "string" ? { bundlePath: flags.o } : {},
566
+ ...typeof flags["audio-manifest-out"] === "string" ? { audioManifestPath: resolve(flags["audio-manifest-out"]) } : {},
567
+ ...flags["no-placeholder-speech"] === true ? { leaveOutPlaceholderSpeech: true } : {}
568
+ });
546
569
  if (!commitWrites(plan.writes)) return 1;
547
570
  for (const w of plan.writes) console.log(`wrote ${w.path}`);
548
571
  return 0;
@@ -749,6 +772,78 @@ ${count} issue(s)${advisory}`);
749
772
  console.log(`wrote ${flags.o} - ${n} line(s)${flags.all === true ? "" : " (ready to record)"}`);
750
773
  return 0;
751
774
  }
775
+ case "placeholder-speech": {
776
+ const loaded = loadProject(positionals[0] ?? ".");
777
+ const ps = loaded.project.placeholderSpeech;
778
+ let scene;
779
+ if (typeof flags.scene === "string") {
780
+ const found = loaded.scenes.find((sc) => sc.id === flags.scene || sc.name === flags.scene);
781
+ if (!found) {
782
+ console.error(`placeholder-speech: no scene called "${flags.scene}"`);
783
+ return 2;
784
+ }
785
+ scene = found.id;
786
+ }
787
+ const plan = planPlaceholderSpeech(loaded, { ...scene ? { scene } : {}, ...flags.force === true ? { force: "all" } : {} });
788
+ if (!ps || !plan) {
789
+ console.error("placeholder-speech: this project doesn't have Placeholder Speech on (Project settings > Placeholder Speech, which needs Audio Folders)");
790
+ return 2;
791
+ }
792
+ if (!plan.accent) {
793
+ console.error("placeholder-speech: choose an accent first (Project settings > Placeholder Speech): the main language doesn't say which");
794
+ return 2;
795
+ }
796
+ const skipped = Object.entries(plan.skipped).map(([why, n]) => `${n} ${why}`).join(", ");
797
+ console.log(`${plan.tasks.length} line(s) to make into ${plan.folder}${skipped ? ` (left alone: ${skipped})` : ""}`);
798
+ if (flags["dry-run"] === true) {
799
+ for (const t of plan.tasks) console.log(` ${t.beatId} ${t.character} ${t.request.voice}`);
800
+ return 0;
801
+ }
802
+ if (!plan.tasks.length) return 0;
803
+ const key = resolveKeyFile(loaded.root, ps.keyFile);
804
+ if (!key.path) {
805
+ const msg = `placeholder-speech: no Google key on this machine (${ps.keyFile ? `${ps.keyFile} isn't here, and ` : ""}GOOGLE_APPLICATION_CREDENTIALS isn't set, and there's no gcloud sign-in)`;
806
+ if (flags.soft === true) {
807
+ console.log(msg.replace("placeholder-speech: ", "note: "));
808
+ return 0;
809
+ }
810
+ console.error(msg);
811
+ return 1;
812
+ }
813
+ let account;
814
+ try {
815
+ account = readGoogleKey(key.path);
816
+ } catch (e) {
817
+ console.error(`placeholder-speech: ${describeSpeechError(e)}`);
818
+ return 1;
819
+ }
820
+ mkdirSync(plan.folder, { recursive: true });
821
+ const keepOut = ps.keepOutOfVersionControl === true;
822
+ const result = await runPlaceholderSpeech(plan.tasks, {
823
+ token: tokenSource(account),
824
+ write: async (path, bytes) => {
825
+ if (keepOut) {
826
+ writeFileSync(path, bytes);
827
+ return true;
828
+ }
829
+ const r = writeBinaryFile(path, bytes);
830
+ if (r.success) return true;
831
+ if (r.status === "locked") return false;
832
+ throw new Error(r.message);
833
+ },
834
+ onProgress: (done, total) => {
835
+ if (process.stdout.isTTY) process.stdout.write(`\r${done}/${total}`);
836
+ }
837
+ });
838
+ if (process.stdout.isTTY) process.stdout.write("\n");
839
+ console.log(`made ${result.made.length}${result.skippedLocked.length ? `, skipped ${result.skippedLocked.length} (open elsewhere)` : ""}${result.failed.length ? `, failed ${result.failed.length}` : ""}`);
840
+ for (const f of result.failed) console.error(` ${f.beatId}: ${f.error}`);
841
+ if (result.stopped) {
842
+ console.error(`placeholder-speech: stopped: ${result.stopped.message}${result.notTried ? ` (${result.notTried} not tried)` : ""}`);
843
+ return 1;
844
+ }
845
+ return result.failed.length ? 1 : 0;
846
+ }
752
847
  case "loc-import": {
753
848
  const file = positionals[0];
754
849
  if (!file) return usage("loc-import", "no file given");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "The `patter` CLI: validate, format, export, play, test coverage, localise, and merge Patter projects.",
5
5
  "keywords": [
6
6
  "patter",
@@ -42,9 +42,9 @@
42
42
  "build:standalone:others": "node scripts/build-standalone.mjs --targets=linux-x64,linux-arm64,windows-x64"
43
43
  },
44
44
  "dependencies": {
45
- "@patterkit/core": "1.0.0",
46
- "@patterkit/ops": "1.0.0",
45
+ "@patterkit/core": "1.1.0",
46
+ "@patterkit/ops": "1.1.0",
47
47
  "@wildwinter/simple-vc-lib": "^0.5.2",
48
- "@wildwinter/toolkit": "^1.0.0"
48
+ "@wildwinter/toolkit": "^1.2.0"
49
49
  }
50
50
  }