@replayablejs/cli 0.1.0-alpha.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Replayable contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @replayablejs/cli
2
+
3
+ Run assets, configuration, development, build and export commands.
4
+
5
+ Part of [Replayable](https://github.com/replayablejs/replayable) **0.1.0-alpha.0**.
6
+ APIs may change during the alpha series.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ pnpm add -D @replayablejs/cli@0.1.0-alpha.0
12
+ ```
13
+
14
+ ## Public surface
15
+
16
+ Executable: `replayable`. This package has no library export entry.
17
+
18
+ [Usage and reference](https://github.com/replayablejs/replayable/blob/main/docs/reference/cli.md).
19
+ The package manifest defines supported import paths; internal source files are not public APIs.
20
+
21
+ ## Development
22
+
23
+ From the repository root, install with `pnpm install --frozen-lockfile` and build dependencies
24
+ with `pnpm build`. Run `pnpm --filter @replayablejs/cli test` for this package's tests.
25
+
26
+ ## License
27
+
28
+ Original code is [MIT licensed](https://github.com/replayablejs/replayable/blob/main/LICENSE). Bundled third-party resources retain
29
+ their accompanying license terms.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ // oxlint-disable-next-line import/no-unassigned-import -- This shim executes the compiled CLI.
4
+ import '../dist/cli.mjs';
package/dist/cli.mjs ADDED
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+ import { inspect, styleText } from "node:util";
3
+ import { Command, InvalidArgumentError } from "commander";
4
+ import { assetConfigSchema, buildAssets } from "@replayablejs/assets";
5
+ import { join, relative, resolve } from "node:path";
6
+ import { createJiti } from "jiti";
7
+ import { buildProject, servePreview } from "@replayablejs/build";
8
+ import { createVariants, replayableConfigSchema } from "@replayablejs/config";
9
+ import { createHash } from "node:crypto";
10
+ import { mkdir, writeFile } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import open from "open";
13
+ import { createScript, createStyleSheet, dump, themes } from "@poppinss/dumper/html";
14
+ import { toHtml } from "hast-util-to-html";
15
+ import { h } from "hastscript";
16
+ import { exportProject } from "@replayablejs/export";
17
+ //#region src/errors/report-cli-error.ts
18
+ /** Reports actionable nested failures without a stack dump and marks the command as failed. */
19
+ function reportCliError(error) {
20
+ console.error(formatError(error, /* @__PURE__ */ new Set()));
21
+ process.exitCode = 1;
22
+ }
23
+ /** Preserves aggregate members and causes, guarding against circular error chains. */
24
+ function formatError(error, ancestors) {
25
+ if (!(error instanceof Error)) return typeof error === "string" ? error : inspect(error, {
26
+ colors: false,
27
+ depth: 2
28
+ });
29
+ if (ancestors.has(error)) return "[Circular error]";
30
+ ancestors.add(error);
31
+ const lines = [error.message || error.name];
32
+ if (error instanceof AggregateError) {
33
+ const members = error.errors;
34
+ for (const member of members) lines.push(indent(formatError(member, ancestors)));
35
+ }
36
+ if (error.cause !== void 0) lines.push(indent(`Caused by: ${formatError(error.cause, ancestors)}`));
37
+ ancestors.delete(error);
38
+ return lines.join("\n");
39
+ }
40
+ /** Keeps multiline validation messages aligned beneath their parent failure. */
41
+ function indent(message) {
42
+ return message.split("\n").map((line) => ` ${line}`).join("\n");
43
+ }
44
+ //#endregion
45
+ //#region src/config/load-default-export.ts
46
+ const jiti = createJiti(import.meta.url, { interopDefault: false });
47
+ /** Loads the default export from one project configuration module. */
48
+ async function loadDefaultExport(path) {
49
+ const file = resolve(path);
50
+ const module = await jiti.import(file);
51
+ if (typeof module !== "object" || module === null || !("default" in module)) throw new Error(`Configuration ${path} must have a default export.`);
52
+ return module.default;
53
+ }
54
+ //#endregion
55
+ //#region src/commands/assets.ts
56
+ function registerAssetsCommand(program) {
57
+ program.command("assets").description("Build optimized, typed assets").option("-c, --config <file>", "path to the asset configuration", "replayable.assets.ts").action(async ({ config }) => build(config));
58
+ }
59
+ async function build(configPath) {
60
+ const config = assetConfigSchema.parse(await loadDefaultExport(configPath));
61
+ const result = await buildAssets(config);
62
+ console.log(`Built ${result.emittedAssets} asset${result.emittedAssets === 1 ? "" : "s"} (${result.emittedFiles} file${result.emittedFiles === 1 ? "" : "s"}) into ${result.outputDirectory}`);
63
+ }
64
+ //#endregion
65
+ //#region src/commands/build.ts
66
+ /** Registers the command that builds every configured playable variant. */
67
+ function registerBuildCommand(program) {
68
+ program.command("build").description("Build every configured playable variant").option("-c, --config <file>", "path to the Replayable config", "replayable.config.ts").action(async ({ config }) => runBuildCommand(config));
69
+ }
70
+ /** Loads one project config, builds it, and reports the generated playables. */
71
+ async function runBuildCommand(configPath) {
72
+ const config = replayableConfigSchema.parse(await loadDefaultExport(configPath));
73
+ const result = await buildProject(config);
74
+ for (const variant of result.variants) {
75
+ const htmlFile = relative(process.cwd(), variant.htmlFile);
76
+ console.log(`Built ${variant.variantId} → ${htmlFile}`);
77
+ }
78
+ const count = result.variants.length;
79
+ console.log(`Built ${count} playable variant${count === 1 ? "" : "s"}.`);
80
+ }
81
+ //#endregion
82
+ //#region src/config/config-viewer.css?inline
83
+ var config_viewer_default = ":root{color-scheme:dark;background:#061626}body{margin:0;padding:32px}main{max-width:1200px;margin:0 auto}h1{color:#d6deeb;margin:0 0 16px;font:600 24px/1.4 system-ui,sans-serif}dl{grid-template-columns:max-content 1fr;margin:0 0 24px;font:14px/1.6 system-ui,sans-serif;display:grid}dt{color:#7fdbca;padding-right:24px;font-weight:600}dd{color:#d6deeb;margin:0}.dumper-dump pre{margin:0;overflow:auto}\n";
84
+ //#endregion
85
+ //#region src/config/render-config-viewer.ts
86
+ /** Renders the viewer; authored values remain text, while Dumper owns its HTML. */
87
+ function renderConfigViewer(config, variants) {
88
+ const head = h("head", [
89
+ h("meta", { charSet: "UTF-8" }),
90
+ h("meta", {
91
+ name: "viewport",
92
+ content: "width=device-width, initial-scale=1.0"
93
+ }),
94
+ h("title", "Replayable Variants"),
95
+ h("style", createStyleSheet() + config_viewer_default),
96
+ h("script", createScript())
97
+ ]);
98
+ const summary = h("dl", [
99
+ h("dt", "Project"),
100
+ h("dd", config.name),
101
+ h("dt", "Entry"),
102
+ h("dd", config.entry),
103
+ h("dt", "Variants"),
104
+ h("dd", String(variants.length))
105
+ ]);
106
+ const content = dump(variants, {
107
+ expand: true,
108
+ styles: themes.nightOwl
109
+ });
110
+ return `<!doctype html>
111
+ <html lang="en">
112
+ ${toHtml(head)}
113
+ <body><main>
114
+ <h1>Replayable Variants</h1>
115
+ ${toHtml(summary)}
116
+ ${content}
117
+ </main></body>
118
+ </html>\n`;
119
+ }
120
+ //#endregion
121
+ //#region src/config/open-config-viewer.ts
122
+ /** Writes a self-contained, collapsible variant viewer and opens it in the browser. */
123
+ async function openConfigViewer(config, variants, configPath) {
124
+ const viewerDirectory = join(tmpdir(), "replayable");
125
+ const viewerPath = join(viewerDirectory, createViewerFileName(configPath));
126
+ await mkdir(viewerDirectory, { recursive: true });
127
+ await writeFile(viewerPath, renderConfigViewer(config, variants), "utf8");
128
+ await open(viewerPath);
129
+ }
130
+ /** Gives each project config a stable temporary viewer file without exposing its path. */
131
+ function createViewerFileName(configPath) {
132
+ return `variants-${createHash("sha256").update(resolve(configPath)).digest("hex").slice(0, 12)}.html`;
133
+ }
134
+ //#endregion
135
+ //#region src/commands/config.ts
136
+ /** Registers the command that presents every configured playable variant. */
137
+ function registerConfigCommand(program) {
138
+ program.command("config").description("Show the configured playable variants").option("-c, --config <file>", "path to the Replayable config", "replayable.config.ts").option("--json", "print machine-readable JSON").action(async ({ config, json }) => showConfig(config, json ?? false));
139
+ }
140
+ /** Loads one project config and presents its concrete playable variants. */
141
+ async function showConfig(configPath, json) {
142
+ const config = replayableConfigSchema.parse(await loadDefaultExport(configPath));
143
+ const variants = createVariants(config);
144
+ if (json) {
145
+ console.log(JSON.stringify(variants, null, 2));
146
+ return;
147
+ }
148
+ await openConfigViewer(config, variants, configPath);
149
+ console.log("Opened the playable variants in your browser.");
150
+ }
151
+ //#endregion
152
+ //#region src/commands/dev.ts
153
+ /** Registers the command that runs one playable variant with Vite HMR. */
154
+ function registerDevCommand(program) {
155
+ program.command("dev").description("Run one playable variant locally").option("-c, --config <file>", "path to the Replayable config", "replayable.config.ts").option("--host <host>", "hostname or IP address to expose").option("--language <language>", "playable language to run").option("-o, --open", "open the playable in the default browser").option("-p, --port <port>", "development server port", parsePort).option("--version <version>", "playable version to run").action(async (options) => runDevCommand(options));
156
+ }
157
+ /** Loads the project, starts local development, and reports reachable URLs. */
158
+ async function runDevCommand(options) {
159
+ const { config: configPath, ...developmentOptions } = options;
160
+ const config = replayableConfigSchema.parse(await loadDefaultExport(configPath));
161
+ const result = await servePreview(config, {
162
+ ...developmentOptions,
163
+ projectRoot: process.cwd()
164
+ });
165
+ logDevelopmentValue("Playable", result.variantId);
166
+ for (const url of result.localUrls) logDevelopmentValue("Local", url);
167
+ for (const url of result.networkUrls) logDevelopmentValue("Network", url);
168
+ console.log();
169
+ }
170
+ /** Prints one aligned and colored development server value. */
171
+ function logDevelopmentValue(label, value) {
172
+ const coloredLabel = styleText("green", label.padEnd(10));
173
+ const coloredValue = styleText("cyan", value);
174
+ console.log(`${coloredLabel}${coloredValue}`);
175
+ }
176
+ /** Parses and validates Commander's string-valued port option. */
177
+ function parsePort(value) {
178
+ const port = Number(value);
179
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new InvalidArgumentError("Port must be an integer from 1 through 65535.");
180
+ return port;
181
+ }
182
+ //#endregion
183
+ //#region src/commands/export.ts
184
+ /** Registers the command that creates upload-ready network artifacts. */
185
+ function registerExportCommand(program) {
186
+ program.command("export").description("Export every configured playable build").option("-c, --config <file>", "path to the Replayable config", "replayable.config.ts").option("-o, --output <directory>", "directory for exported artifacts").action(async (options) => runExportCommand(options));
187
+ }
188
+ /** Loads one project config, exports every variant, and reports their files. */
189
+ async function runExportCommand(options) {
190
+ const config = replayableConfigSchema.parse(await loadDefaultExport(options.config));
191
+ const result = await exportProject(config, {
192
+ ...options.output === void 0 ? {} : { outputDirectory: options.output },
193
+ projectRoot: process.cwd()
194
+ });
195
+ for (const variant of result.variants) console.log(`Exported ${variant.variantId} → ${relative(process.cwd(), variant.file)}`);
196
+ const count = result.variants.length;
197
+ console.log(`Exported ${count} playable variant${count === 1 ? "" : "s"}.`);
198
+ }
199
+ //#endregion
200
+ //#region src/program.ts
201
+ function createProgram() {
202
+ const program = new Command().name("replayable").description("Build playable ads with Replayable").showHelpAfterError();
203
+ registerBuildCommand(program);
204
+ registerDevCommand(program);
205
+ registerExportCommand(program);
206
+ registerConfigCommand(program);
207
+ registerAssetsCommand(program);
208
+ return program;
209
+ }
210
+ //#endregion
211
+ //#region src/cli.ts
212
+ createProgram().parseAsync().catch(reportCliError);
213
+ //#endregion
214
+ export {};
215
+
216
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":["styles"],"sources":["../src/errors/report-cli-error.ts","../src/config/load-default-export.ts","../src/commands/assets.ts","../src/commands/build.ts","../src/config/config-viewer.css?inline","../src/config/render-config-viewer.ts","../src/config/open-config-viewer.ts","../src/commands/config.ts","../src/commands/dev.ts","../src/commands/export.ts","../src/program.ts","../src/cli.ts"],"sourcesContent":["import { inspect } from 'node:util';\n\n/** Reports actionable nested failures without a stack dump and marks the command as failed. */\nexport function reportCliError(error: unknown): void {\n console.error(formatError(error, new Set()));\n process.exitCode = 1;\n}\n\n/** Preserves aggregate members and causes, guarding against circular error chains. */\nfunction formatError(error: unknown, ancestors: Set<Error>): string {\n if (!(error instanceof Error)) {\n return typeof error === 'string' ? error : inspect(error, { colors: false, depth: 2 });\n }\n if (ancestors.has(error)) {\n return '[Circular error]';\n }\n\n ancestors.add(error);\n const lines = [error.message || error.name];\n\n if (error instanceof AggregateError) {\n const members: readonly unknown[] = error.errors;\n for (const member of members) {\n lines.push(indent(formatError(member, ancestors)));\n }\n }\n if (error.cause !== undefined) {\n lines.push(indent(`Caused by: ${formatError(error.cause, ancestors)}`));\n }\n\n ancestors.delete(error);\n return lines.join('\\n');\n}\n\n/** Keeps multiline validation messages aligned beneath their parent failure. */\nfunction indent(message: string): string {\n return message\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n');\n}\n","import { resolve } from 'node:path';\n\nimport { createJiti } from 'jiti';\n\nconst jiti = createJiti(import.meta.url, { interopDefault: false });\n\n/** Loads the default export from one project configuration module. */\nexport async function loadDefaultExport(path: string): Promise<unknown> {\n const file = resolve(path);\n const module: unknown = await jiti.import(file);\n\n if (typeof module !== 'object' || module === null || !('default' in module)) {\n throw new Error(`Configuration ${path} must have a default export.`);\n }\n\n return module.default;\n}\n","import { assetConfigSchema, buildAssets } from '@replayablejs/assets';\nimport type { Command } from 'commander';\n\nimport { loadDefaultExport } from '../config/load-default-export.js';\nimport type { AssetsOptions } from '../types/commands.js';\n\nexport function registerAssetsCommand(program: Command): void {\n program\n .command('assets')\n .description('Build optimized, typed assets')\n .option('-c, --config <file>', 'path to the asset configuration', 'replayable.assets.ts')\n .action(async ({ config }: AssetsOptions) => build(config));\n}\n\nasync function build(configPath: string): Promise<void> {\n const config = assetConfigSchema.parse(await loadDefaultExport(configPath));\n const result = await buildAssets(config);\n\n console.log(\n `Built ${result.emittedAssets} asset${result.emittedAssets === 1 ? '' : 's'} (${result.emittedFiles} file${result.emittedFiles === 1 ? '' : 's'}) into ${result.outputDirectory}`,\n );\n}\n","import { relative } from 'node:path';\n\nimport { buildProject } from '@replayablejs/build';\nimport { replayableConfigSchema } from '@replayablejs/config';\nimport type { Command } from 'commander';\n\nimport { loadDefaultExport } from '../config/load-default-export.js';\nimport type { BuildOptions } from '../types/commands.js';\n\n/** Registers the command that builds every configured playable variant. */\nexport function registerBuildCommand(program: Command): void {\n program\n .command('build')\n .description('Build every configured playable variant')\n .option('-c, --config <file>', 'path to the Replayable config', 'replayable.config.ts')\n .action(async ({ config }: BuildOptions) => runBuildCommand(config));\n}\n\n/** Loads one project config, builds it, and reports the generated playables. */\nasync function runBuildCommand(configPath: string): Promise<void> {\n const config = replayableConfigSchema.parse(await loadDefaultExport(configPath));\n const result = await buildProject(config);\n\n for (const variant of result.variants) {\n const htmlFile = relative(process.cwd(), variant.htmlFile);\n\n console.log(`Built ${variant.variantId} → ${htmlFile}`);\n }\n\n const count = result.variants.length;\n\n console.log(`Built ${count} playable variant${count === 1 ? '' : 's'}.`);\n}\n",":root {\n color-scheme: dark;\n background: #061626;\n}\n\nbody {\n margin: 0;\n padding: 32px;\n}\n\nmain {\n margin: 0 auto;\n max-width: 1200px;\n}\n\nh1 {\n color: #d6deeb;\n font:\n 600 24px/1.4 system-ui,\n sans-serif;\n margin: 0 0 16px;\n}\n\ndl {\n display: grid;\n font:\n 14px/1.6 system-ui,\n sans-serif;\n grid-template-columns: max-content 1fr;\n margin: 0 0 24px;\n}\n\ndt {\n color: #7fdbca;\n font-weight: 600;\n padding-right: 24px;\n}\n\ndd {\n color: #d6deeb;\n margin: 0;\n}\n\n.dumper-dump pre {\n margin: 0;\n overflow: auto;\n}\n","import { createScript, createStyleSheet, dump, themes } from '@poppinss/dumper/html';\nimport type { PlayableVariant, ReplayableConfig } from '@replayablejs/config';\nimport { toHtml } from 'hast-util-to-html';\nimport { h } from 'hastscript';\n\nimport styles from './config-viewer.css?inline';\n\n/** Renders the viewer; authored values remain text, while Dumper owns its HTML. */\nexport function renderConfigViewer(\n config: ReplayableConfig,\n variants: readonly PlayableVariant[],\n): string {\n const head = h('head', [\n h('meta', { charSet: 'UTF-8' }),\n h('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }),\n h('title', 'Replayable Variants'),\n h('style', createStyleSheet() + styles),\n h('script', createScript()),\n ]);\n const summary = h('dl', [\n h('dt', 'Project'),\n h('dd', config.name),\n h('dt', 'Entry'),\n h('dd', config.entry),\n h('dt', 'Variants'),\n h('dd', String(variants.length)),\n ]);\n const content = dump(variants, { expand: true, styles: themes.nightOwl });\n\n // Both serializers own escaping. The shell embeds their completed markup unchanged.\n return `<!doctype html>\n<html lang=\"en\">\n ${toHtml(head)}\n <body><main>\n <h1>Replayable Variants</h1>\n ${toHtml(summary)}\n ${content}\n </main></body>\n</html>\\n`;\n}\n","import { createHash } from 'node:crypto';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\nimport type { PlayableVariant, ReplayableConfig } from '@replayablejs/config';\nimport open from 'open';\n\nimport { renderConfigViewer } from './render-config-viewer.js';\n\n/** Writes a self-contained, collapsible variant viewer and opens it in the browser. */\nexport async function openConfigViewer(\n config: ReplayableConfig,\n variants: readonly PlayableVariant[],\n configPath: string,\n): Promise<void> {\n const viewerDirectory = join(tmpdir(), 'replayable');\n const viewerPath = join(viewerDirectory, createViewerFileName(configPath));\n\n await mkdir(viewerDirectory, { recursive: true });\n await writeFile(viewerPath, renderConfigViewer(config, variants), 'utf8');\n await open(viewerPath);\n}\n\n/** Gives each project config a stable temporary viewer file without exposing its path. */\nfunction createViewerFileName(configPath: string): string {\n const configId = createHash('sha256').update(resolve(configPath)).digest('hex').slice(0, 12);\n\n return `variants-${configId}.html`;\n}\n","import { createVariants, replayableConfigSchema } from '@replayablejs/config';\nimport type { Command } from 'commander';\n\nimport { loadDefaultExport } from '../config/load-default-export.js';\nimport { openConfigViewer } from '../config/open-config-viewer.js';\nimport type { ConfigOptions } from '../types/commands.js';\n\n/** Registers the command that presents every configured playable variant. */\nexport function registerConfigCommand(program: Command): void {\n program\n .command('config')\n .description('Show the configured playable variants')\n .option('-c, --config <file>', 'path to the Replayable config', 'replayable.config.ts')\n .option('--json', 'print machine-readable JSON')\n .action(async ({ config, json }: ConfigOptions) => showConfig(config, json ?? false));\n}\n\n/** Loads one project config and presents its concrete playable variants. */\nasync function showConfig(configPath: string, json: boolean): Promise<void> {\n const config = replayableConfigSchema.parse(await loadDefaultExport(configPath));\n const variants = createVariants(config);\n\n if (json) {\n console.log(JSON.stringify(variants, null, 2));\n\n return;\n }\n\n await openConfigViewer(config, variants, configPath);\n console.log('Opened the playable variants in your browser.');\n}\n","import { styleText } from 'node:util';\n\nimport { servePreview } from '@replayablejs/build';\nimport { replayableConfigSchema } from '@replayablejs/config';\nimport { InvalidArgumentError, type Command } from 'commander';\n\nimport { loadDefaultExport } from '../config/load-default-export.js';\nimport type { DevOptions } from '../types/commands.js';\n\n/** Registers the command that runs one playable variant with Vite HMR. */\nexport function registerDevCommand(program: Command): void {\n program\n .command('dev')\n .description('Run one playable variant locally')\n .option('-c, --config <file>', 'path to the Replayable config', 'replayable.config.ts')\n .option('--host <host>', 'hostname or IP address to expose')\n .option('--language <language>', 'playable language to run')\n .option('-o, --open', 'open the playable in the default browser')\n .option('-p, --port <port>', 'development server port', parsePort)\n .option('--version <version>', 'playable version to run')\n .action(async (options: DevOptions) => runDevCommand(options));\n}\n\n/** Loads the project, starts local development, and reports reachable URLs. */\nasync function runDevCommand(options: DevOptions): Promise<void> {\n const { config: configPath, ...developmentOptions } = options;\n const config = replayableConfigSchema.parse(await loadDefaultExport(configPath));\n const result = await servePreview(config, {\n ...developmentOptions,\n projectRoot: process.cwd(),\n });\n\n logDevelopmentValue('Playable', result.variantId);\n\n for (const url of result.localUrls) {\n logDevelopmentValue('Local', url);\n }\n\n for (const url of result.networkUrls) {\n logDevelopmentValue('Network', url);\n }\n\n console.log();\n}\n\n/** Prints one aligned and colored development server value. */\nfunction logDevelopmentValue(label: string, value: string): void {\n const coloredLabel = styleText('green', label.padEnd(10));\n const coloredValue = styleText('cyan', value);\n\n console.log(`${coloredLabel}${coloredValue}`);\n}\n\n/** Parses and validates Commander's string-valued port option. */\nfunction parsePort(value: string): number {\n const port = Number(value);\n\n if (!Number.isInteger(port) || port < 1 || port > 65_535) {\n throw new InvalidArgumentError('Port must be an integer from 1 through 65535.');\n }\n\n return port;\n}\n","import { relative } from 'node:path';\n\nimport { replayableConfigSchema } from '@replayablejs/config';\nimport { exportProject } from '@replayablejs/export';\nimport type { Command } from 'commander';\n\nimport { loadDefaultExport } from '../config/load-default-export.js';\nimport type { ExportOptions } from '../types/commands.js';\n\n/** Registers the command that creates upload-ready network artifacts. */\nexport function registerExportCommand(program: Command): void {\n program\n .command('export')\n .description('Export every configured playable build')\n .option('-c, --config <file>', 'path to the Replayable config', 'replayable.config.ts')\n .option('-o, --output <directory>', 'directory for exported artifacts')\n .action(async (options: ExportOptions) => runExportCommand(options));\n}\n\n/** Loads one project config, exports every variant, and reports their files. */\nasync function runExportCommand(options: ExportOptions): Promise<void> {\n const config = replayableConfigSchema.parse(await loadDefaultExport(options.config));\n const result = await exportProject(config, {\n ...(options.output === undefined ? {} : { outputDirectory: options.output }),\n projectRoot: process.cwd(),\n });\n\n for (const variant of result.variants) {\n console.log(`Exported ${variant.variantId} → ${relative(process.cwd(), variant.file)}`);\n }\n\n const count = result.variants.length;\n\n console.log(`Exported ${count} playable variant${count === 1 ? '' : 's'}.`);\n}\n","import { Command } from 'commander';\n\nimport { registerAssetsCommand } from './commands/assets.js';\nimport { registerBuildCommand } from './commands/build.js';\nimport { registerConfigCommand } from './commands/config.js';\nimport { registerDevCommand } from './commands/dev.js';\nimport { registerExportCommand } from './commands/export.js';\n\nexport function createProgram(): Command {\n const program = new Command()\n .name('replayable')\n .description('Build playable ads with Replayable')\n .showHelpAfterError();\n\n registerBuildCommand(program);\n registerDevCommand(program);\n registerExportCommand(program);\n registerConfigCommand(program);\n registerAssetsCommand(program);\n\n return program;\n}\n","#!/usr/bin/env node\n\nimport { reportCliError } from './errors/report-cli-error.js';\nimport { createProgram } from './program.js';\n\ncreateProgram().parseAsync().catch(reportCliError);\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@replayablejs/cli",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Command-line interface for Replayable",
5
+ "homepage": "https://github.com/replayablejs/replayable#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/replayablejs/replayable/issues"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/replayablejs/replayable.git",
13
+ "directory": "packages/cli"
14
+ },
15
+ "bin": {
16
+ "replayable": "./bin/replayable.mjs"
17
+ },
18
+ "files": [
19
+ "bin",
20
+ "dist"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "@poppinss/dumper": "0.7.0",
29
+ "commander": "15.0.0",
30
+ "hast-util-to-html": "9.0.5",
31
+ "hastscript": "9.0.1",
32
+ "jiti": "2.7.0",
33
+ "open": "11.0.1",
34
+ "@replayablejs/assets": "0.1.0-alpha.0",
35
+ "@replayablejs/build": "0.1.0-alpha.0",
36
+ "@replayablejs/export": "0.1.0-alpha.0",
37
+ "@replayablejs/config": "0.1.0-alpha.0"
38
+ },
39
+ "devDependencies": {
40
+ "@tsdown/css": "0.22.14",
41
+ "@types/node": "26.2.0",
42
+ "tsdown": "0.22.14",
43
+ "typescript": "7.0.2",
44
+ "vitest": "4.1.10"
45
+ },
46
+ "engines": {
47
+ "node": ">=24.0.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsdown",
51
+ "dev": "tsdown --watch",
52
+ "lint": "oxlint --type-aware --max-warnings 0 .",
53
+ "test": "vitest run",
54
+ "typecheck": "tsc --noEmit"
55
+ }
56
+ }