@bitmagic/cli 0.1.53-dev.2 → 0.1.53-dev.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.
Files changed (46) hide show
  1. package/README.md +37 -12
  2. package/dist/cli.d.ts +16 -0
  3. package/dist/cli.js +16 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/cover.d.ts +5 -0
  6. package/dist/commands/cover.js +3 -0
  7. package/dist/commands/cover.js.map +1 -1
  8. package/dist/commands/forge.d.ts +4 -0
  9. package/dist/commands/forge.js +9 -0
  10. package/dist/commands/forge.js.map +1 -1
  11. package/dist/commands/generate.d.ts +3 -0
  12. package/dist/commands/generate.js +21 -7
  13. package/dist/commands/generate.js.map +1 -1
  14. package/dist/commands/init.d.ts +4 -0
  15. package/dist/commands/init.js +12 -1
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/publish.d.ts +12 -0
  18. package/dist/commands/publish.js +15 -0
  19. package/dist/commands/publish.js.map +1 -1
  20. package/dist/generate/model.d.ts +2 -0
  21. package/dist/generate/model.js +6 -1
  22. package/dist/generate/model.js.map +1 -1
  23. package/dist/generate/prop.d.ts +2 -0
  24. package/dist/generate/prop.js +2 -2
  25. package/dist/generate/prop.js.map +1 -1
  26. package/dist/generate/vehicle.d.ts +2 -0
  27. package/dist/generate/vehicle.js +1 -0
  28. package/dist/generate/vehicle.js.map +1 -1
  29. package/dist/render/qr-terminal.d.ts +56 -0
  30. package/dist/render/qr-terminal.js +135 -0
  31. package/dist/render/qr-terminal.js.map +1 -0
  32. package/dist/render/qr.d.ts +51 -0
  33. package/dist/render/qr.js +516 -0
  34. package/dist/render/qr.js.map +1 -0
  35. package/dist/scaffold/project-files.js +8 -0
  36. package/dist/scaffold/project-files.js.map +1 -1
  37. package/dist/scaffold/suggest-template.d.ts +2 -0
  38. package/dist/scaffold/suggest-template.js +2 -1
  39. package/dist/scaffold/suggest-template.js.map +1 -1
  40. package/dist/telemetry/command-context.d.ts +12 -0
  41. package/dist/telemetry/command-context.js +7 -0
  42. package/dist/telemetry/command-context.js.map +1 -1
  43. package/dist/telemetry/creator-prompt.d.ts +35 -0
  44. package/dist/telemetry/creator-prompt.js +45 -0
  45. package/dist/telemetry/creator-prompt.js.map +1 -0
  46. package/package.json +4 -4
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Drawing a QR code into the terminal, so the URL `bitmagic publish` just printed can be scanned
3
+ * off the screen instead of retyped into a phone. The Creator has had this since it shipped — the
4
+ * publish dialog ends on a QR — and the CLI's publish is the same moment: `--visibility private`
5
+ * puts a real, phone-reachable URL on a game nobody else can find, which is exactly a "try it on
6
+ * my phone" build.
7
+ *
8
+ * The contract is `inline-image.ts`'s, deliberately: the code is strictly ADDITIONAL. The caller
9
+ * prints the URL first, unconditionally; this either draws underneath it or does nothing at all. It
10
+ * never replaces a line and it never reports failure as an error, because an agent tool is usually
11
+ * what reads this output.
12
+ *
13
+ * Where it DIVERGES from that file is the gate. `supportsInlineImage` is an allowlist of terminals
14
+ * known to implement the iTerm2 protocol, because the ones that do not print a screenful of raw
15
+ * base64. Nothing here is exotic: half-block characters and SGR colour are universal, and tmux
16
+ * passes both through rather than swallowing them. So the gate below asks what the terminal can do
17
+ * — is it a terminal at all, is it wide enough — and never who it is.
18
+ */
19
+ import { encodeQr } from './qr.js';
20
+ /** Written as an escape rather than a literal control byte, so the source stays greppable. */
21
+ const ESC = '\u001b';
22
+ /**
23
+ * Black on bright white, and the reset that closes every line.
24
+ *
25
+ * Explicit on purpose. A scanner wants dark modules on a light field, and the creator's terminal is
26
+ * probably a dark theme — left to inherit, the code would come out inverted, which many scanners
27
+ * cope with and some do not. The reset ends each line so the colour cannot bleed into a wrapped
28
+ * line or the prompt. Bright white (107) rather than plain white (47) because the latter is a light
29
+ * grey in most palettes, and contrast is the one thing a scanner cannot make up for.
30
+ */
31
+ const PAINT = `${ESC}[30;107m`;
32
+ const RESET = `${ESC}[0m`;
33
+ /**
34
+ * Quiet-zone width in modules, matching the `margin: 2` the Creator passes `QRCode.toDataURL` —
35
+ * a value already proven to scan in production. The spec asks for 4; if scans ever prove flaky,
36
+ * that is the knob, and it costs 4 columns and 1 line.
37
+ */
38
+ const QUIET_ZONE = 2;
39
+ /**
40
+ * Two module rows per terminal row, drawn with half-blocks.
41
+ *
42
+ * This is what makes the code square. Terminal cells are roughly twice as tall as they are wide, so
43
+ * one module per cell would stretch the symbol into a rectangle no scanner would read; half-blocks
44
+ * put two module rows in one cell and bring the aspect ratio back to 1:1.
45
+ */
46
+ const BOTH_DARK = '█'; // full block
47
+ const TOP_DARK = '▀'; // upper half block
48
+ const BOTTOM_DARK = '▄'; // lower half block
49
+ const BOTH_LIGHT = ' ';
50
+ /**
51
+ * Whether this terminal can show a scannable code at all.
52
+ *
53
+ * Not an allowlist — see the file header. `TERM=dumb` is the one terminal that answers no on its
54
+ * own behalf, and a non-TTY means something is reading this rather than looking at it.
55
+ */
56
+ export function supportsTerminalQr(env, isTTY) {
57
+ if (!isTTY)
58
+ return false;
59
+ if (env.BITMAGIC_NO_QR)
60
+ return false;
61
+ return env.TERM !== 'dumb';
62
+ }
63
+ /** How many columns the drawn code occupies, quiet zone included. */
64
+ export function terminalQrWidth(matrix) {
65
+ return matrix.length + QUIET_ZONE * 2;
66
+ }
67
+ /**
68
+ * The code as terminal lines, quiet zone included and each line self-contained.
69
+ *
70
+ * Exported so a test can read the output without a TTY. The padded symbol has an odd number of
71
+ * module rows — the module count is always odd and the quiet zone adds an even number — so the
72
+ * final terminal row pairs its top half against a light row that is not in the symbol. That is
73
+ * correct rather than a rounding artefact: it reads as one more row of quiet zone.
74
+ */
75
+ export function terminalQrLines(matrix) {
76
+ const width = terminalQrWidth(matrix);
77
+ const height = width;
78
+ const dark = (row, col) => {
79
+ const r = row - QUIET_ZONE;
80
+ const c = col - QUIET_ZONE;
81
+ if (r < 0 || c < 0 || r >= matrix.length || c >= matrix.length)
82
+ return false;
83
+ return matrix[r][c];
84
+ };
85
+ const lines = [];
86
+ for (let row = 0; row < height; row += 2) {
87
+ let line = '';
88
+ for (let col = 0; col < width; col += 1) {
89
+ const top = dark(row, col);
90
+ const bottom = row + 1 < height && dark(row + 1, col);
91
+ if (top && bottom)
92
+ line += BOTH_DARK;
93
+ else if (top)
94
+ line += TOP_DARK;
95
+ else if (bottom)
96
+ line += BOTTOM_DARK;
97
+ else
98
+ line += BOTH_LIGHT;
99
+ }
100
+ lines.push(`${PAINT}${line}${RESET}`);
101
+ }
102
+ return lines;
103
+ }
104
+ /**
105
+ * Draw `text` as a QR code if this terminal can show one. Returns whether anything was drawn, so a
106
+ * caller can decide what else to say — never throws, and never writes when unsupported.
107
+ *
108
+ * The caption and the code go out in a single write, so the creator never sees an invitation to
109
+ * scan with nothing under it.
110
+ */
111
+ export function renderTerminalQr(text, options = {}) {
112
+ const env = options.env ?? process.env;
113
+ const isTTY = options.isTTY ?? process.stdout.isTTY === true;
114
+ if (!supportsTerminalQr(env, isTTY))
115
+ return false;
116
+ const matrix = encodeQr(text);
117
+ if (matrix === null)
118
+ return false;
119
+ // A code wider than the terminal wraps, and a wrapped code is not a code — it is noise that
120
+ // looks like output. Better to leave the plain URL standing on its own.
121
+ const columns = options.columns ?? process.stdout.columns;
122
+ if (typeof columns === 'number' && columns < terminalQrWidth(matrix))
123
+ return false;
124
+ const caption = options.caption ?? 'Scan to play on your phone:';
125
+ const write = options.write ?? ((chunk) => process.stdout.write(chunk));
126
+ try {
127
+ write(`\n${caption}\n\n${terminalQrLines(matrix).join('\n')}\n\n`);
128
+ return true;
129
+ }
130
+ catch {
131
+ // A closed or non-writable stdout is not worth failing a successful publish over.
132
+ return false;
133
+ }
134
+ }
135
+ //# sourceMappingURL=qr-terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr-terminal.js","sourceRoot":"","sources":["../../src/render/qr-terminal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,8FAA8F;AAC9F,MAAM,GAAG,GAAG,QAAQ,CAAC;AAErB;;;;;;;;GAQG;AACH,MAAM,KAAK,GAAG,GAAG,GAAG,UAAU,CAAC;AAC/B,MAAM,KAAK,GAAG,GAAG,GAAG,KAAK,CAAC;AAE1B;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,aAAa;AACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,mBAAmB;AACzC,MAAM,WAAW,GAAG,GAAG,CAAC,CAAC,mBAAmB;AAC5C,MAAM,UAAU,GAAG,GAAG,CAAC;AAOvB;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAkB,EAAE,KAAc;IACnE,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,GAAG,CAAC,cAAc;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC;AAC7B,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,eAAe,CAAC,MAAmB;IACjD,OAAO,MAAM,CAAC,MAAM,GAAG,UAAU,GAAG,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,MAAmB;IACjD,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,KAAK,CAAC;IACrB,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,GAAW,EAAW,EAAE;QACjD,MAAM,CAAC,GAAG,GAAG,GAAG,UAAU,CAAC;QAC3B,MAAM,CAAC,GAAG,GAAG,GAAG,UAAU,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC7E,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3B,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;YACtD,IAAI,GAAG,IAAI,MAAM;gBAAE,IAAI,IAAI,SAAS,CAAC;iBAChC,IAAI,GAAG;gBAAE,IAAI,IAAI,QAAQ,CAAC;iBAC1B,IAAI,MAAM;gBAAE,IAAI,IAAI,WAAW,CAAC;;gBAChC,IAAI,IAAI,UAAU,CAAC;QAC1B,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,UAAmC,EAAE;IAClF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;IAC7D,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAElD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAElC,4FAA4F;IAC5F,wEAAwE;IACxE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;IAC1D,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,6BAA6B,CAAC;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,KAAa,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAChF,IAAI,CAAC;QACH,KAAK,CAAC,KAAK,OAAO,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,kFAAkF;QAClF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * A QR encoder, so `bitmagic publish` can hand the creator's phone the URL it just printed.
3
+ *
4
+ * The Creator does this server-side already — `PublishController.generateQRCode` calls the `qrcode`
5
+ * package and the publish dialog shows the PNG. The CLI cannot follow it there: `qrcode` drags in
6
+ * `dijkstrajs`, `pngjs` and `yargs@15`, and `@bitmagic/cli` is a published package with four
7
+ * deliberately-chosen runtime dependencies. So the encoder lives here instead, cut down to exactly
8
+ * what a published-game URL needs:
9
+ *
10
+ * - **Byte mode only.** A URL is bytes. Numeric, alphanumeric and kanji modes would encode it
11
+ * smaller in theory and never in practice, because `https://` alone rules out alphanumeric.
12
+ * - **Error correction level M only**, matching the Creator's `errorCorrectionLevel: 'M'` — a code
13
+ * scanned from this terminal and one scanned from the publish dialog then behave identically.
14
+ * - **Versions 1-9 only.** The character-count indicator is 8 bits up to version 9 and 16 bits
15
+ * from version 10, so stopping here removes a branch and four tables. Version 9 holds 180 bytes;
16
+ * the longest URL this ever sees is the dev portal's, around 60. Anything longer returns null
17
+ * rather than growing the tables — see `encodeQr`.
18
+ *
19
+ * Structure follows ISO/IEC 18004. Every table below is a literal with the spec's own name for it,
20
+ * and `__tests__/qr.test.ts` checks each one against fixtures generated by the `qrcode` package, so
21
+ * a transposed digit fails loudly instead of producing a code that scans as garbage.
22
+ */
23
+ /**
24
+ * The spec's four penalty rules (8.8.2), summed. Lower is better.
25
+ *
26
+ * Scored over the entire symbol including function patterns, as the spec requires — the finders'
27
+ * own 1:1:3:1:1 ratio is exactly what rule 3 hunts for elsewhere, and excluding them would let a
28
+ * mask hide a false finder against a real one.
29
+ */
30
+ export declare function maskPenalty(matrix: boolean[][]): number;
31
+ /**
32
+ * Encode `text` with an explicitly chosen mask.
33
+ *
34
+ * Exported for the fixture tests, which pin the mask so they compare structure — codewords, ECC,
35
+ * interleaving, function patterns, format bits — rather than a mask heuristic. Callers outside the
36
+ * tests want `encodeQr`.
37
+ */
38
+ export declare function encodeQrWithMask(text: string, mask: number): boolean[][] | null;
39
+ /**
40
+ * Encode `text` as a matrix of modules, row-major, true meaning dark. Null when it does not fit.
41
+ *
42
+ * The mask is chosen by the spec's penalty rules. That choice is a legibility heuristic and not a
43
+ * correctness property — all eight masks produce a valid, scannable symbol, and a decoder reads the
44
+ * chosen one out of the format information either way.
45
+ *
46
+ * There is no quiet zone here: it belongs to whatever draws the symbol, which knows what it is
47
+ * drawing onto. `qr-terminal.ts` paints its own.
48
+ */
49
+ export declare function encodeQr(text: string): boolean[][] | null;
50
+ /** The longest text this encoder can hold, in bytes. Exported so a caller can explain a refusal. */
51
+ export declare const MAX_BYTES: number;