zephyr-cli 0.0.3 → 0.1.3-next.6

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 (55) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.json +22 -0
  3. package/LICENSE +40 -21
  4. package/README.md +90 -17
  5. package/dist/cli.d.ts +25 -0
  6. package/dist/cli.js +141 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/deploy.d.ts +12 -0
  9. package/dist/commands/deploy.js +60 -0
  10. package/dist/commands/deploy.js.map +1 -0
  11. package/dist/commands/run.d.ts +9 -0
  12. package/dist/commands/run.js +161 -0
  13. package/dist/commands/run.js.map +1 -0
  14. package/dist/index.js +48 -60
  15. package/dist/index.js.map +1 -0
  16. package/dist/lib/build-stats.d.ts +7 -0
  17. package/dist/lib/build-stats.js +12 -0
  18. package/dist/lib/build-stats.js.map +1 -0
  19. package/dist/lib/command-detector.d.ts +38 -0
  20. package/dist/lib/command-detector.js +453 -0
  21. package/dist/lib/command-detector.js.map +1 -0
  22. package/dist/lib/config-readers.d.ts +37 -0
  23. package/dist/lib/config-readers.js +239 -0
  24. package/dist/lib/config-readers.js.map +1 -0
  25. package/dist/lib/extract-assets.d.ts +6 -0
  26. package/dist/lib/extract-assets.js +95 -0
  27. package/dist/lib/extract-assets.js.map +1 -0
  28. package/dist/lib/shell-parser.d.ts +40 -0
  29. package/dist/lib/shell-parser.js +190 -0
  30. package/dist/lib/shell-parser.js.map +1 -0
  31. package/dist/lib/spawn-helper.d.ts +14 -0
  32. package/dist/lib/spawn-helper.js +36 -0
  33. package/dist/lib/spawn-helper.js.map +1 -0
  34. package/dist/lib/upload.d.ts +14 -0
  35. package/dist/lib/upload.js +32 -0
  36. package/dist/lib/upload.js.map +1 -0
  37. package/dist/package.json +48 -0
  38. package/dist/tsconfig.tsbuildinfo +1 -0
  39. package/jest.config.ts +10 -0
  40. package/package.json +38 -21
  41. package/project.json +34 -0
  42. package/src/cli.ts +150 -0
  43. package/src/commands/deploy.ts +74 -0
  44. package/src/commands/run.ts +196 -0
  45. package/src/index.ts +58 -0
  46. package/src/lib/build-stats.ts +13 -0
  47. package/src/lib/command-detector.ts +600 -0
  48. package/src/lib/config-readers.ts +269 -0
  49. package/src/lib/extract-assets.ts +111 -0
  50. package/src/lib/shell-parser.ts +229 -0
  51. package/src/lib/spawn-helper.ts +49 -0
  52. package/src/lib/upload.ts +39 -0
  53. package/tsconfig.json +22 -0
  54. package/tsconfig.lib.json +10 -0
  55. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeCommand = executeCommand;
4
+ const node_child_process_1 = require("node:child_process");
5
+ /**
6
+ * Execute a command with full stdio passthrough. All stdin, stdout, and stderr are
7
+ * proxied between the parent and child process.
8
+ *
9
+ * @param parsed - The parsed command to execute
10
+ * @param cwd - The working directory
11
+ * @returns Promise that resolves with exit code and signal
12
+ */
13
+ async function executeCommand(parsed, cwd) {
14
+ return new Promise((resolve, reject) => {
15
+ const { command, args, envVars } = parsed;
16
+ // Merge environment variables
17
+ const env = Object.assign(Object.assign({}, process.env), envVars);
18
+ // Spawn the command
19
+ const child = (0, node_child_process_1.spawn)(command, args, {
20
+ cwd,
21
+ env,
22
+ stdio: 'inherit', // This passes stdin, stdout, and stderr through
23
+ shell: true, // Use shell to handle complex commands
24
+ });
25
+ child.on('error', (error) => {
26
+ reject(error);
27
+ });
28
+ child.on('close', (code, signal) => {
29
+ resolve({
30
+ exitCode: code !== null && code !== void 0 ? code : 1,
31
+ signal,
32
+ });
33
+ });
34
+ });
35
+ }
36
+ //# sourceMappingURL=spawn-helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-helper.js","sourceRoot":"","sources":["../../src/lib/spawn-helper.ts"],"names":[],"mappings":";;AAgBA,wCAgCC;AAhDD,2DAA2C;AAQ3C;;;;;;;GAOG;AACI,KAAK,UAAU,cAAc,CAClC,MAAqB,EACrB,GAAW;IAEX,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAE1C,8BAA8B;QAC9B,MAAM,GAAG,mCACJ,OAAO,CAAC,GAAG,GACX,OAAO,CACX,CAAC;QAEF,oBAAoB;QACpB,MAAM,KAAK,GAAG,IAAA,0BAAK,EAAC,OAAO,EAAE,IAAI,EAAE;YACjC,GAAG;YACH,GAAG;YACH,KAAK,EAAE,SAAS,EAAE,gDAAgD;YAClE,KAAK,EAAE,IAAI,EAAE,uCAAuC;SACrD,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YACjC,OAAO,CAAC;gBACN,QAAQ,EAAE,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,CAAC;gBACnB,MAAM;aACP,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { ZeBuildAssetsMap } from 'zephyr-edge-contract';
2
+ import type { ZephyrEngine } from 'zephyr-agent';
3
+ export interface UploadOptions {
4
+ zephyr_engine: ZephyrEngine;
5
+ assetsMap: ZeBuildAssetsMap;
6
+ }
7
+ /**
8
+ * Orchestrate the upload process:
9
+ *
10
+ * 1. Start a new build
11
+ * 2. Upload assets with build stats
12
+ * 3. Finish the build
13
+ */
14
+ export declare function uploadAssets(options: UploadOptions): Promise<void>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadAssets = uploadAssets;
4
+ const zephyr_agent_1 = require("zephyr-agent");
5
+ const build_stats_1 = require("./build-stats");
6
+ /**
7
+ * Orchestrate the upload process:
8
+ *
9
+ * 1. Start a new build
10
+ * 2. Upload assets with build stats
11
+ * 3. Finish the build
12
+ */
13
+ async function uploadAssets(options) {
14
+ const { zephyr_engine, assetsMap } = options;
15
+ try {
16
+ // Start a new build
17
+ await zephyr_engine.start_new_build();
18
+ // Generate build stats
19
+ const buildStats = await (0, build_stats_1.getBuildStats)(zephyr_engine);
20
+ // Upload assets and finish the build
21
+ await zephyr_engine.upload_assets({
22
+ assetsMap,
23
+ buildStats,
24
+ });
25
+ await zephyr_engine.build_finished();
26
+ }
27
+ catch (error) {
28
+ (0, zephyr_agent_1.logFn)('error', zephyr_agent_1.ZephyrError.format(error));
29
+ throw error;
30
+ }
31
+ }
32
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/lib/upload.ts"],"names":[],"mappings":";;AAiBA,oCAqBC;AApCD,+CAAkD;AAClD,+CAA8C;AAO9C;;;;;;GAMG;AACI,KAAK,UAAU,YAAY,CAAC,OAAsB;IACvD,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAE7C,IAAI,CAAC;QACH,oBAAoB;QACpB,MAAM,aAAa,CAAC,eAAe,EAAE,CAAC;QAEtC,uBAAuB;QACvB,MAAM,UAAU,GAAG,MAAM,IAAA,2BAAa,EAAC,aAAa,CAAC,CAAC;QAEtD,qCAAqC;QACrC,MAAM,aAAa,CAAC,aAAa,CAAC;YAChC,SAAS;YACT,UAAU;SACX,CAAC,CAAC;QAEH,MAAM,aAAa,CAAC,cAAc,EAAE,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAA,oBAAK,EAAC,OAAO,EAAE,0BAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "zephyr-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for running build commands and uploading assets to Zephyr",
5
+ "keywords": [
6
+ "zephyr",
7
+ "cli",
8
+ "deploy",
9
+ "build",
10
+ "runner"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/ZephyrCloudIO/zephyr-packages.git",
15
+ "directory": "libs/zephyr-cli"
16
+ },
17
+ "license": "Apache-2.0",
18
+ "author": {
19
+ "name": "ZephyrCloudIO",
20
+ "url": "https://github.com/ZephyrCloudIO"
21
+ },
22
+ "type": "commonjs",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "bin": {
26
+ "ze-cli": "dist/index.js"
27
+ },
28
+ "scripts": {
29
+ "build": "nx run zephyr-cli:build",
30
+ "patch-version": "pnpm version"
31
+ },
32
+ "dependencies": {
33
+ "cosmiconfig": "^9.0.0",
34
+ "jsonc-parser": "^3.3.1",
35
+ "tslib": "catalog:typescript",
36
+ "zephyr-agent": "workspace:*"
37
+ },
38
+ "devDependencies": {
39
+ "@types/jest": "catalog:typescript",
40
+ "@types/node": "catalog:typescript",
41
+ "@typescript-eslint/eslint-plugin": "catalog:eslint",
42
+ "ts-jest": "catalog:typescript"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ }
48
+ }
@@ -0,0 +1 @@
1
+ {"version":"5.9.3"}
package/jest.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ export default {
2
+ displayName: 'zephyr-cli',
3
+ preset: '../../jest.preset.js',
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
7
+ },
8
+ moduleFileExtensions: ['ts', 'js', 'html'],
9
+ coverageDirectory: '../../coverage/libs/zephyr-cli',
10
+ };
package/package.json CHANGED
@@ -1,31 +1,48 @@
1
1
  {
2
2
  "name": "zephyr-cli",
3
- "version": "0.0.3",
4
- "description": "CLI utility for Zephyr projects",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "zephyr": "dist/index.js"
8
- },
9
- "scripts": {
10
- "build": "tsc",
11
- "prepare": "npm run build",
12
- "test": "echo \"Error: no test specified\" && exit 1"
13
- },
3
+ "version": "0.1.3-next.6",
4
+ "description": "CLI tool for running build commands and uploading assets to Zephyr",
14
5
  "keywords": [
15
- "cli",
16
6
  "zephyr",
17
- "tool"
18
- ],
19
- "author": "Zephyr Team",
20
- "license": "MIT",
21
- "files": [
22
- "dist"
7
+ "cli",
8
+ "deploy",
9
+ "build",
10
+ "runner"
23
11
  ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/ZephyrCloudIO/zephyr-packages.git",
15
+ "directory": "libs/zephyr-cli"
16
+ },
17
+ "license": "Apache-2.0",
18
+ "author": {
19
+ "name": "ZephyrCloudIO",
20
+ "url": "https://github.com/ZephyrCloudIO"
21
+ },
22
+ "type": "commonjs",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "bin": {
26
+ "ze-cli": "dist/index.js"
27
+ },
24
28
  "dependencies": {
25
- "chalk": "^4.1.2"
29
+ "cosmiconfig": "^9.0.0",
30
+ "jsonc-parser": "^3.3.1",
31
+ "tslib": "^2.8.1",
32
+ "zephyr-agent": "0.1.3-next.6"
26
33
  },
27
34
  "devDependencies": {
28
- "@types/node": "^16.11.12",
29
- "typescript": "^4.5.4"
35
+ "@types/jest": "29.5.14",
36
+ "@types/node": "^22.13.11",
37
+ "@typescript-eslint/eslint-plugin": "^8.27.0",
38
+ "ts-jest": "^29.2.6"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ },
44
+ "scripts": {
45
+ "build": "nx run zephyr-cli:build",
46
+ "patch-version": "pnpm version"
30
47
  }
31
48
  }
package/project.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "zephyr-cli",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/zephyr-cli/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "build": {
9
+ "executor": "@nx/js:tsc",
10
+ "outputs": ["{options.outputPath}"],
11
+ "options": {
12
+ "rootDir": "libs/zephyr-cli/src",
13
+ "outputPath": "libs/zephyr-cli/dist",
14
+ "tsConfig": "libs/zephyr-cli/tsconfig.lib.json",
15
+ "main": "libs/zephyr-cli/src/index.ts"
16
+ }
17
+ },
18
+ "nx-release-publish": {
19
+ "options": {
20
+ "packageRoot": "dist\\{projectRoot}"
21
+ }
22
+ },
23
+ "lint": {
24
+ "executor": "@nx/eslint:lint"
25
+ },
26
+ "test": {
27
+ "executor": "@nx/jest:jest",
28
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
29
+ "options": {
30
+ "jestConfig": "libs/zephyr-cli/jest.config.ts"
31
+ }
32
+ }
33
+ }
34
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,150 @@
1
+ export interface CliOptions {
2
+ command: 'run' | 'deploy';
3
+ commandLine?: string; // For 'run' command
4
+ directory?: string; // For 'deploy' command
5
+ target?: 'web' | 'ios' | 'android';
6
+ verbose?: boolean;
7
+ ssr?: boolean;
8
+ }
9
+
10
+ /**
11
+ * Parse command line arguments.
12
+ *
13
+ * Syntax:
14
+ *
15
+ * - Ze-cli [options] <command> [args...] - run command (default)
16
+ * - Ze-cli deploy <directory> [options] - deploy command
17
+ *
18
+ * Examples:
19
+ *
20
+ * - Ze-cli --ssr pnpm build
21
+ * - Ze-cli tsc
22
+ * - Ze-cli NODE_ENV=production webpack
23
+ * - Ze-cli deploy ./dist
24
+ * - Ze-cli deploy ./dist --ssr
25
+ */
26
+ export function parseArgs(args: string[]): CliOptions {
27
+ const options: CliOptions = {
28
+ command: 'run', // Default to 'run'
29
+ };
30
+
31
+ // Find where flags end and the command/subcommand begins
32
+ let commandStartIndex = -1;
33
+ const flags: string[] = [];
34
+
35
+ for (let i = 0; i < args.length; i++) {
36
+ const arg = args[i];
37
+
38
+ if (arg === '--help' || arg === '-h') {
39
+ printHelp();
40
+ process.exit(0);
41
+ } else if (arg === '--version' || arg === '-v') {
42
+ // Version is handled by the caller
43
+ options.verbose = true;
44
+ } else if (arg === '--ssr') {
45
+ options.ssr = true;
46
+ flags.push(arg);
47
+ } else if (arg === '--target' || arg === '-t') {
48
+ const value = args[++i];
49
+ if (value && ['web', 'ios', 'android'].includes(value)) {
50
+ options.target = value as 'web' | 'ios' | 'android';
51
+ }
52
+ flags.push(arg, value);
53
+ } else if (arg === '--verbose') {
54
+ options.verbose = true;
55
+ flags.push(arg);
56
+ } else if (!arg.startsWith('-')) {
57
+ // First non-flag argument
58
+ commandStartIndex = i;
59
+ break;
60
+ }
61
+ }
62
+
63
+ if (commandStartIndex === -1) {
64
+ printHelp();
65
+ process.exit(1);
66
+ }
67
+
68
+ const firstArg = args[commandStartIndex];
69
+
70
+ // Check if it's a subcommand
71
+ if (firstArg === 'deploy') {
72
+ options.command = 'deploy';
73
+ const directory = args[commandStartIndex + 1];
74
+
75
+ if (!directory) {
76
+ console.error('Error: deploy command requires a directory argument');
77
+ console.error('Usage: ze-cli deploy <directory> [options]');
78
+ process.exit(1);
79
+ }
80
+
81
+ options.directory = directory;
82
+
83
+ // Parse any additional flags after the directory
84
+ for (let i = commandStartIndex + 2; i < args.length; i++) {
85
+ const arg = args[i];
86
+
87
+ if (arg === '--ssr') {
88
+ options.ssr = true;
89
+ } else if (arg === '--target' || arg === '-t') {
90
+ const value = args[++i];
91
+ if (value && ['web', 'ios', 'android'].includes(value)) {
92
+ options.target = value as 'web' | 'ios' | 'android';
93
+ }
94
+ } else if (arg === '--verbose') {
95
+ options.verbose = true;
96
+ }
97
+ }
98
+ } else {
99
+ // It's a run command - everything from commandStartIndex onwards is the command
100
+ options.command = 'run';
101
+ options.commandLine = args.slice(commandStartIndex).join(' ');
102
+ }
103
+
104
+ return options;
105
+ }
106
+
107
+ function printHelp(): void {
108
+ console.log(`
109
+ Usage: ze-cli [options] <command> [args...]
110
+ ze-cli deploy <directory> [options]
111
+
112
+ Run a build command and automatically upload assets to Zephyr, or deploy
113
+ pre-built assets from a directory.
114
+
115
+ Commands:
116
+ <command> [args...] Run a build command and upload (default)
117
+ deploy <directory> Upload pre-built assets from a directory
118
+
119
+ Options:
120
+ --ssr Mark this snapshot as server-side rendered
121
+ --target, -t <target> Build target: web, ios, or android (default: web)
122
+ --verbose Enable verbose output
123
+ --help, -h Show this help message
124
+
125
+ Examples:
126
+ # Run build commands
127
+ ze-cli pnpm build
128
+ ze-cli yarn build
129
+ ze-cli tsc
130
+ ze-cli NODE_ENV=production webpack
131
+ ze-cli --ssr pnpm build
132
+
133
+ # Deploy pre-built assets
134
+ ze-cli deploy ./dist
135
+ ze-cli deploy ./dist --ssr
136
+ ze-cli deploy ./build --target ios
137
+
138
+ How it works:
139
+ - For run commands, ze-cli executes your build command and automatically
140
+ detects the output directory to upload assets.
141
+ - For deploy commands, ze-cli uploads assets from the specified directory.
142
+ - All stdout/stderr from build commands are passed through.
143
+ - ze-cli logs are written to stderr only.
144
+
145
+ Note: No configuration file is needed. Zephyr will automatically detect
146
+ application information from your package.json and git repository.
147
+
148
+ For more information: https://docs.zephyr-cloud.io/cli
149
+ `);
150
+ }
@@ -0,0 +1,74 @@
1
+ import { access, constants } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { ZephyrEngine, logFn, ZephyrError, ZeErrors } from 'zephyr-agent';
4
+ import { extractAssetsFromDirectory } from '../lib/extract-assets';
5
+ import { uploadAssets } from '../lib/upload';
6
+
7
+ export interface DeployOptions {
8
+ directory: string;
9
+ target?: 'web' | 'ios' | 'android';
10
+ verbose?: boolean;
11
+ ssr?: boolean;
12
+ cwd: string;
13
+ }
14
+
15
+ /**
16
+ * Deploy command: Upload pre-built assets from a directory to Zephyr. This is similar to
17
+ * the standalone zephyr-cli tool.
18
+ */
19
+ export async function deployCommand(options: DeployOptions): Promise<void> {
20
+ const { directory, target, verbose, ssr, cwd } = options;
21
+
22
+ // Resolve the directory path
23
+ const directoryPath = resolve(cwd, directory);
24
+
25
+ // Check if directory exists
26
+ try {
27
+ await access(directoryPath, constants.F_OK);
28
+ } catch {
29
+ throw new ZephyrError(ZeErrors.ERR_UNKNOWN, {
30
+ message: `Directory does not exist: ${directoryPath}`,
31
+ });
32
+ }
33
+
34
+ if (verbose) {
35
+ logFn('info', `Uploading assets from: ${directoryPath}`);
36
+ }
37
+
38
+ // Initialize ZephyrEngine with project root context
39
+ const zephyr_engine = await ZephyrEngine.create({
40
+ builder: 'unknown',
41
+ context: cwd,
42
+ });
43
+
44
+ // Set build target if specified
45
+ if (target) {
46
+ zephyr_engine.env.target = target;
47
+ }
48
+
49
+ // Set SSR flag if specified
50
+ if (ssr) {
51
+ zephyr_engine.env.ssr = true;
52
+ }
53
+
54
+ // Extract assets from the directory
55
+ if (verbose) {
56
+ logFn('info', 'Extracting assets from directory...');
57
+ }
58
+ const assetsMap = await extractAssetsFromDirectory(directoryPath);
59
+
60
+ if (verbose) {
61
+ const assetCount = Object.keys(assetsMap).length;
62
+ logFn('info', `Found ${assetCount} assets to upload`);
63
+ }
64
+
65
+ // Upload assets
66
+ await uploadAssets({
67
+ zephyr_engine,
68
+ assetsMap,
69
+ });
70
+
71
+ if (verbose) {
72
+ logFn('info', 'Upload completed successfully');
73
+ }
74
+ }
@@ -0,0 +1,196 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { relative, resolve } from 'node:path';
3
+ import { ZeErrors, ZephyrEngine, ZephyrError } from 'zephyr-agent';
4
+ import { detectMultipleCommands } from '../lib/command-detector';
5
+ import { extractAssetsFromDirectory } from '../lib/extract-assets';
6
+ import { parseShellCommand, splitCommands } from '../lib/shell-parser';
7
+ import { executeCommand } from '../lib/spawn-helper';
8
+ import { uploadAssets } from '../lib/upload';
9
+
10
+ export interface RunOptions {
11
+ commandLine: string;
12
+ target?: 'web' | 'ios' | 'android';
13
+ verbose?: boolean;
14
+ ssr?: boolean;
15
+ cwd: string;
16
+ }
17
+
18
+ /** Run command: Execute a build command and automatically upload the resulting assets. */
19
+ export async function runCommand(options: RunOptions): Promise<void> {
20
+ const { commandLine, target, verbose, ssr, cwd } = options;
21
+
22
+ // Log to stderr so it doesn't interfere with command output
23
+ const log = (level: 'info' | 'warn' | 'error', message: string) => {
24
+ if (level === 'info' && !verbose) {
25
+ return;
26
+ }
27
+ // All ze-cli logs go to stderr
28
+ console.error(`[ze-cli] ${message}`);
29
+ };
30
+
31
+ // Parse the shell command - check if there are multiple commands
32
+ log('info', `Parsing command: ${commandLine}`);
33
+ const individualCommands = splitCommands(commandLine);
34
+
35
+ if (individualCommands.length > 1) {
36
+ log('info', `Detected ${individualCommands.length} commands to execute`);
37
+ }
38
+
39
+ // Detect multiple commands and their output directories
40
+ const multiDetection = await detectMultipleCommands(commandLine, cwd);
41
+ const { commands: detectedCommands, outputDirs, commonOutputDir } = multiDetection;
42
+
43
+ // Log detected tools
44
+ // Flatten commands if there are sub-commands
45
+ const allCommands: typeof detectedCommands = [];
46
+ for (const detected of detectedCommands) {
47
+ if (detected.subCommands && detected.subCommands.length > 0) {
48
+ // If there are sub-commands, log them instead of the parent
49
+ allCommands.push(...detected.subCommands);
50
+ } else {
51
+ allCommands.push(detected);
52
+ }
53
+ }
54
+
55
+ for (let i = 0; i < allCommands.length; i++) {
56
+ const detected = allCommands[i];
57
+ log('info', `Command ${i + 1}: ${detected.tool}`);
58
+
59
+ if (detected.configFile) {
60
+ log('info', ` Config file: ${detected.configFile}`);
61
+ }
62
+
63
+ if (detected.outputDir) {
64
+ const absoluteOutputDir = resolve(cwd, detected.outputDir);
65
+ log('info', ` Output directory: ${relative(cwd, absoluteOutputDir) || '.'}`);
66
+ }
67
+ }
68
+
69
+ // If multiple output directories detected, show common ancestor
70
+ if (outputDirs.length > 1 && commonOutputDir) {
71
+ log(
72
+ 'info',
73
+ `Multiple output directories detected, using common ancestor: ${relative(cwd, commonOutputDir) || '.'}`
74
+ );
75
+ }
76
+
77
+ // Warn about dynamic configs
78
+ if (!outputDirs.length) {
79
+ console.error('[ze-cli] WARNING: Configuration is too dynamic to analyze!');
80
+ console.error('[ze-cli] ');
81
+ console.error(
82
+ '[ze-cli] Your build tool uses a JavaScript configuration file that cannot be'
83
+ );
84
+ console.error('[ze-cli] statically analyzed. This means ze-cli cannot automatically');
85
+ console.error('[ze-cli] detect the output directory.');
86
+ console.error('[ze-cli] ');
87
+ console.error('[ze-cli] Recommendations:');
88
+ console.error('[ze-cli] 1. Use a Zephyr bundler plugin:');
89
+ console.error('[ze-cli] - @zephyrcloud/webpack-plugin');
90
+ console.error('[ze-cli] - @zephyrcloud/rollup-plugin');
91
+ console.error('[ze-cli] - @zephyrcloud/vite-plugin');
92
+ console.error('[ze-cli] - etc.');
93
+ console.error('[ze-cli] 2. Or use "ze-cli deploy <dir>" after building');
94
+ console.error('[ze-cli] ');
95
+ console.error('[ze-cli] For more info: https://docs.zephyr-cloud.io/integrations');
96
+ console.error('[ze-cli] ');
97
+ }
98
+
99
+ // Display warnings from all commands
100
+ for (const detected of detectedCommands) {
101
+ if (detected.warnings.length > 0 && !detected.isDynamicConfig) {
102
+ for (const warning of detected.warnings) {
103
+ console.error(`[ze-cli] Warning: ${warning}`);
104
+ }
105
+ }
106
+ }
107
+
108
+ // Execute all build commands sequentially
109
+ for (let i = 0; i < individualCommands.length; i++) {
110
+ const cmd = individualCommands[i];
111
+ log('info', `Executing command ${i + 1}/${individualCommands.length}: ${cmd}`);
112
+
113
+ try {
114
+ const parsed = parseShellCommand(cmd);
115
+ const result = await executeCommand(parsed, cwd);
116
+
117
+ if (result.exitCode !== 0) {
118
+ throw new ZephyrError(ZeErrors.ERR_UNKNOWN, {
119
+ message: `Build command failed with exit code ${result.exitCode}`,
120
+ });
121
+ }
122
+
123
+ log('info', `Command ${i + 1} completed successfully`);
124
+ } catch (error) {
125
+ throw new ZephyrError(ZeErrors.ERR_UNKNOWN, {
126
+ message: `Failed to execute command: ${cmd}\n${(error as Error).message}`,
127
+ });
128
+ }
129
+ }
130
+
131
+ log('info', 'All build commands completed successfully');
132
+
133
+ // Determine which output directory to use
134
+ let outputDir: string | null = null;
135
+
136
+ if (commonOutputDir) {
137
+ outputDir = commonOutputDir;
138
+ } else if (detectedCommands.length === 1 && detectedCommands[0].outputDir) {
139
+ outputDir = resolve(cwd, detectedCommands[0].outputDir);
140
+ }
141
+
142
+ // If we couldn't detect the output directory, stop here
143
+ if (!outputDir) {
144
+ console.error('[ze-cli] ');
145
+ console.error('[ze-cli] Could not detect output directory. Skipping upload.');
146
+ console.error('[ze-cli] Please use "ze-cli deploy <dir>" to upload manually.');
147
+ console.error('[ze-cli] ');
148
+ return;
149
+ }
150
+
151
+ log('info', `Using output directory: ${relative(cwd, outputDir) || '.'}`);
152
+
153
+ // Check if output directory exists
154
+ if (!existsSync(outputDir)) {
155
+ throw new ZephyrError(ZeErrors.ERR_UNKNOWN, {
156
+ message: `Output directory does not exist: ${outputDir}`,
157
+ });
158
+ }
159
+
160
+ // Determine the primary build tool for ZephyrEngine
161
+ const primaryTool = detectedCommands[0]?.tool || 'unknown';
162
+
163
+ // Initialize ZephyrEngine with project root context
164
+ log('info', 'Initializing Zephyr Engine...');
165
+ const zephyr_engine = await ZephyrEngine.create({
166
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
+ builder: primaryTool as any,
168
+ context: cwd,
169
+ });
170
+
171
+ // Set build target if specified
172
+ if (target) {
173
+ zephyr_engine.env.target = target;
174
+ }
175
+
176
+ // Set SSR flag if specified
177
+ if (ssr) {
178
+ zephyr_engine.env.ssr = true;
179
+ }
180
+
181
+ // Extract assets from the output directory
182
+ log('info', 'Extracting assets from output directory...');
183
+ const assetsMap = await extractAssetsFromDirectory(outputDir);
184
+
185
+ const assetCount = Object.keys(assetsMap).length;
186
+ log('info', `Found ${assetCount} assets to upload`);
187
+
188
+ // Upload assets
189
+ log('info', 'Uploading assets to Zephyr...');
190
+ await uploadAssets({
191
+ zephyr_engine,
192
+ assetsMap,
193
+ });
194
+
195
+ log('info', 'Upload completed successfully');
196
+ }