@nebula-rn/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.
package/bin/nebula.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const Module = require('module');
6
+ const ts = require('typescript');
7
+
8
+ const compileTypeScriptModule = (module, filename) => {
9
+ const source = fs.readFileSync(filename, 'utf8');
10
+ const compiled = ts.transpileModule(source, {
11
+ compilerOptions: {
12
+ module: ts.ModuleKind.CommonJS,
13
+ target: ts.ScriptTarget.ES2020,
14
+ esModuleInterop: true,
15
+ jsx: ts.JsxEmit.React,
16
+ },
17
+ fileName: filename,
18
+ });
19
+ module._compile(compiled.outputText, filename);
20
+ };
21
+
22
+ require.extensions['.ts'] = compileTypeScriptModule;
23
+ require.extensions['.tsx'] = compileTypeScriptModule;
24
+
25
+ const entryPath = path.join(__dirname, '../src/cli.ts');
26
+ const runtimeModule = new Module(entryPath, module.parent ?? module);
27
+ runtimeModule.filename = entryPath;
28
+ runtimeModule.paths = Module._nodeModulePaths(path.dirname(entryPath));
29
+ compileTypeScriptModule(runtimeModule, entryPath);
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const create_1 = require("./create");
5
+ const miniapp_1 = require("./miniapp");
6
+ const platform_1 = require("./platform");
7
+ const utils_1 = require("./utils");
8
+ async function main() {
9
+ const [, , scope, action, ...extraArgs] = process.argv;
10
+ if (scope === 'create' &&
11
+ (action === 'host' || action === 'miniapp' || action === 'runner')) {
12
+ const flags = (0, utils_1.parseFlags)(extraArgs);
13
+ const projectName = (0, utils_1.omitFlags)(extraArgs, [
14
+ 'directory',
15
+ 'app-id',
16
+ 'display-name',
17
+ 'bundle-id',
18
+ ])[0];
19
+ if (!projectName) {
20
+ (0, utils_1.exitWithError)(`Usage: nebula create ${action} <name> [--directory <path>] [--app-id <id>] [--display-name <name>] [--bundle-id <id>]`);
21
+ }
22
+ const result = (0, create_1.createProject)(action, {
23
+ appId: flags['app-id'],
24
+ bundleId: flags['bundle-id'],
25
+ directory: flags.directory,
26
+ displayName: flags['display-name'],
27
+ name: projectName,
28
+ });
29
+ console.log(`[nebula] Created ${action} project at ${result.targetDir}`);
30
+ return;
31
+ }
32
+ if (scope === 'config' && action === 'server') {
33
+ (0, platform_1.configureServer)(extraArgs);
34
+ return;
35
+ }
36
+ if (scope === 'auth' && action === 'login') {
37
+ await (0, platform_1.loginPlatform)(extraArgs);
38
+ return;
39
+ }
40
+ if (scope === 'auth' && action === 'logout') {
41
+ (0, platform_1.logoutPlatform)();
42
+ return;
43
+ }
44
+ if (scope === 'miniapp' && action === 'upload') {
45
+ await (0, platform_1.uploadMiniApp)(extraArgs);
46
+ return;
47
+ }
48
+ if (scope === 'miniapp') {
49
+ await (0, miniapp_1.runMiniApp)(action, extraArgs);
50
+ return;
51
+ }
52
+ (0, utils_1.exitWithError)('Usage: nebula create <host|miniapp|runner> <name> [...args], nebula miniapp <dev|build|upload> [...args], nebula auth <login|logout> [...args], or nebula config server [--url <url>|--clear]');
53
+ }
54
+ main().catch(error => {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ (0, utils_1.exitWithError)(message);
57
+ });
@@ -0,0 +1,12 @@
1
+ type CreateProjectKind = 'host' | 'miniapp' | 'runner';
2
+ type CreateProjectOptions = {
3
+ name: string;
4
+ directory?: string;
5
+ appId?: string;
6
+ displayName?: string;
7
+ bundleId?: string;
8
+ };
9
+ export declare function createProject(kind: CreateProjectKind, options: CreateProjectOptions): {
10
+ targetDir: string;
11
+ };
12
+ export {};
package/dist/create.js ADDED
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createProject = createProject;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const TEMPLATE_PACKAGE_ROOT = path.resolve(__dirname, '../../template');
40
+ const TEXT_TEMPLATE_EXTENSIONS = new Set([
41
+ '.js',
42
+ '.jsx',
43
+ '.ts',
44
+ '.tsx',
45
+ '.swift',
46
+ '.json',
47
+ '.gradle',
48
+ '.kt',
49
+ '.plist',
50
+ '.storyboard',
51
+ '.pbxproj',
52
+ '.xml',
53
+ '.rb',
54
+ '.xcprivacy',
55
+ '.properties',
56
+ '.md',
57
+ ]);
58
+ const TEXT_TEMPLATE_BASENAMES = new Set([
59
+ '.watchmanconfig',
60
+ '.eslintrc.js',
61
+ '.xcode.env',
62
+ 'Podfile',
63
+ 'Gemfile',
64
+ 'gradlew',
65
+ 'gradlew.bat',
66
+ ]);
67
+ function fail(message) {
68
+ throw new Error(`[nebula] ${message}`);
69
+ }
70
+ function readJson(filePath) {
71
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
72
+ }
73
+ function readPackageVersion(packageDir) {
74
+ const packageJson = readJson(path.join(packageDir, 'package.json'));
75
+ return packageJson.version || '0.0.1';
76
+ }
77
+ function resolvePackageVersions() {
78
+ const packagesDir = path.resolve(__dirname, '../..');
79
+ return {
80
+ sdk: readPackageVersion(path.join(packagesDir, 'nebula-sdk')),
81
+ host: readPackageVersion(path.join(packagesDir, 'host')),
82
+ hostApis: readPackageVersion(path.join(packagesDir, 'host-apis')),
83
+ client: readPackageVersion(path.join(packagesDir, 'api')),
84
+ };
85
+ }
86
+ function toKebabCase(value) {
87
+ return value
88
+ .trim()
89
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
90
+ .replace(/[^a-zA-Z0-9]+/g, '-')
91
+ .replace(/^-+|-+$/g, '')
92
+ .toLowerCase();
93
+ }
94
+ function toPascalCase(value) {
95
+ return value
96
+ .split(/[^a-zA-Z0-9]+/)
97
+ .filter(Boolean)
98
+ .map(segment => segment[0].toUpperCase() + segment.slice(1))
99
+ .join('');
100
+ }
101
+ function toDisplayName(value) {
102
+ return value
103
+ .split(/[^a-zA-Z0-9]+/)
104
+ .filter(Boolean)
105
+ .map(segment => segment[0].toUpperCase() + segment.slice(1))
106
+ .join(' ');
107
+ }
108
+ function toBundleId(value) {
109
+ const normalized = value
110
+ .toLowerCase()
111
+ .replace(/[^a-z0-9]+/g, '.')
112
+ .replace(/^\.+|\.+$/g, '')
113
+ .replace(/\.{2,}/g, '.');
114
+ const segments = normalized
115
+ .split('.')
116
+ .filter(Boolean)
117
+ .map(segment => segment.replace(/^[^a-z]+/, '').replace(/[^a-z0-9]/g, ''))
118
+ .filter(Boolean);
119
+ if (segments.length === 0) {
120
+ return 'com.nebula.app';
121
+ }
122
+ return `com.${segments.join('.')}`;
123
+ }
124
+ function ensureTargetDirectory(targetDir) {
125
+ if (!fs.existsSync(targetDir)) {
126
+ fs.mkdirSync(targetDir, { recursive: true });
127
+ return;
128
+ }
129
+ if (fs.readdirSync(targetDir).length > 0) {
130
+ fail(`Target directory is not empty: ${targetDir}`);
131
+ }
132
+ }
133
+ function getTemplateRoot(kind) {
134
+ const templateRoot = path.join(TEMPLATE_PACKAGE_ROOT, kind);
135
+ if (!fs.existsSync(templateRoot)) {
136
+ fail(`Missing ${kind} template at ${templateRoot}`);
137
+ }
138
+ return templateRoot;
139
+ }
140
+ function isTextTemplateFile(filePath) {
141
+ const basename = path.basename(filePath);
142
+ if (TEXT_TEMPLATE_BASENAMES.has(basename)) {
143
+ return true;
144
+ }
145
+ const extension = path.extname(filePath);
146
+ return TEXT_TEMPLATE_EXTENSIONS.has(extension);
147
+ }
148
+ function replaceTemplateTokens(value, replacements) {
149
+ let nextValue = value;
150
+ for (const [token, replacement] of Object.entries(replacements)) {
151
+ nextValue = nextValue.split(token).join(replacement);
152
+ }
153
+ return nextValue;
154
+ }
155
+ function applyTemplateReplacements(targetRoot, replacements) {
156
+ const visit = (currentPath) => {
157
+ const stats = fs.statSync(currentPath);
158
+ if (stats.isDirectory()) {
159
+ for (const entry of fs.readdirSync(currentPath)) {
160
+ visit(path.join(currentPath, entry));
161
+ }
162
+ return;
163
+ }
164
+ if (!isTextTemplateFile(currentPath)) {
165
+ return;
166
+ }
167
+ const original = fs.readFileSync(currentPath, 'utf8');
168
+ const replaced = replaceTemplateTokens(original, replacements);
169
+ if (original !== replaced) {
170
+ fs.writeFileSync(currentPath, replaced);
171
+ }
172
+ };
173
+ visit(targetRoot);
174
+ }
175
+ function buildReplacements(kind, options, versions) {
176
+ const slug = toKebabCase(options.name);
177
+ const displayName = options.displayName || toDisplayName(options.name);
178
+ const appId = options.appId || slug || 'nebula-miniapp';
179
+ const componentName = kind === 'runner'
180
+ ? toPascalCase(options.name) || 'NebulaDevRunner'
181
+ : kind === 'host'
182
+ ? toPascalCase(options.name) || 'NebulaHostApp'
183
+ : 'NebulaMiniapp';
184
+ const bundleId = options.bundleId || toBundleId(options.name);
185
+ return {
186
+ __PACKAGE_NAME__: slug,
187
+ __DISPLAY_NAME__: displayName,
188
+ __COMPONENT_NAME__: componentName,
189
+ __BUNDLE_ID__: bundleId,
190
+ __APP_ID__: appId,
191
+ __NEBULA_CLIENT_VERSION__: versions.client,
192
+ __NEBULA_HOST_VERSION__: versions.host,
193
+ __NEBULA_HOST_APIS_VERSION__: versions.hostApis,
194
+ __NEBULA_SDK_VERSION__: versions.sdk,
195
+ };
196
+ }
197
+ function copyTemplateProject(kind, targetDir, options) {
198
+ const templateRoot = getTemplateRoot(kind);
199
+ const versions = resolvePackageVersions();
200
+ const replacements = buildReplacements(kind, options, versions);
201
+ ensureTargetDirectory(targetDir);
202
+ fs.cpSync(templateRoot, targetDir, { recursive: true });
203
+ applyTemplateReplacements(targetDir, replacements);
204
+ }
205
+ function createProject(kind, options) {
206
+ const slug = toKebabCase(options.name);
207
+ if (!slug) {
208
+ fail('Project name cannot be empty.');
209
+ }
210
+ const targetDir = path.resolve(process.cwd(), options.directory || options.name);
211
+ copyTemplateProject(kind, targetDir, options);
212
+ return { targetDir };
213
+ }
@@ -0,0 +1,25 @@
1
+ type PageStyle = {
2
+ backgroundColor?: string;
3
+ navigationBarBackgroundColor?: string;
4
+ navigationBarTextColor?: string;
5
+ navigationBarTitleText?: string;
6
+ navigationStyle?: 'default' | 'custom';
7
+ visualEffectInBackground?: 'blur' | 'none';
8
+ };
9
+ export type UserMiniAppConfig = {
10
+ appId: string;
11
+ entryPagePath?: string;
12
+ pages: string[] | Record<string, string>;
13
+ updateStrategy?: 'auto' | 'manual';
14
+ window?: PageStyle;
15
+ };
16
+ export type BuiltMiniAppArtifacts = {
17
+ manifestPath: string;
18
+ iosBundlePath: string;
19
+ androidBundlePath: string;
20
+ iosAssetsDir: string;
21
+ androidAssetsDir: string;
22
+ };
23
+ export declare function buildMiniAppArtifacts(projectRoot: string, workspaceRoot: string, appConfig: UserMiniAppConfig, extraArgs: string[]): Promise<BuiltMiniAppArtifacts>;
24
+ export declare function runMiniApp(action: string, extraArgs: string[]): Promise<void>;
25
+ export {};
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildMiniAppArtifacts = buildMiniAppArtifacts;
7
+ exports.runMiniApp = runMiniApp;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const module_1 = __importDefault(require("module"));
11
+ const utils_1 = require("./utils");
12
+ function registerTypeScriptRequire() {
13
+ const globalKey = '__nebulaTsRequireRegistered';
14
+ if (globalThis[globalKey]) {
15
+ return;
16
+ }
17
+ const workspaceRoot = (0, utils_1.findWorkspaceRoot)(process.cwd());
18
+ const moduleLoadKey = '__nebulaPageConfigModuleLoadPatched';
19
+ if (!globalThis[moduleLoadKey]) {
20
+ const moduleInternal = module_1.default;
21
+ const originalLoad = moduleInternal._load;
22
+ moduleInternal._load = function patchedNebulaModuleLoad(request, parent, isMain) {
23
+ if (request === '@nebula-rn/sdk') {
24
+ const sdkPageConfigEntry = path_1.default.join(workspaceRoot, 'packages', 'nebula-sdk', 'page-config.ts');
25
+ if (fs_1.default.existsSync(sdkPageConfigEntry)) {
26
+ return Reflect.apply(originalLoad, this, [
27
+ sdkPageConfigEntry,
28
+ parent,
29
+ isMain,
30
+ ]);
31
+ }
32
+ }
33
+ return Reflect.apply(originalLoad, this, [request, parent, isMain]);
34
+ };
35
+ globalThis[moduleLoadKey] = true;
36
+ }
37
+ const compileExtension = (module, filename) => {
38
+ const ts = require('typescript');
39
+ const source = fs_1.default.readFileSync(filename, 'utf8');
40
+ const compiled = ts.transpileModule(source, {
41
+ compilerOptions: {
42
+ esModuleInterop: true,
43
+ jsx: ts.JsxEmit.React,
44
+ module: ts.ModuleKind.CommonJS,
45
+ target: ts.ScriptTarget.ES2020,
46
+ },
47
+ fileName: filename,
48
+ });
49
+ module._compile(compiled.outputText, filename);
50
+ };
51
+ require.extensions['.ts'] = compileExtension;
52
+ require.extensions['.tsx'] = compileExtension;
53
+ globalThis[globalKey] = true;
54
+ }
55
+ function normalizeRoute(route) {
56
+ if (!route || route === '/') {
57
+ return '/';
58
+ }
59
+ const normalized = route.replace(/^\/+/, '').replace(/\/+$/, '');
60
+ return `/${normalized}`;
61
+ }
62
+ function normalizeImportPath(fromDir, targetPath) {
63
+ const relativePath = path_1.default
64
+ .relative(fromDir, targetPath)
65
+ .split(path_1.default.sep)
66
+ .join('/')
67
+ .replace(/\.(tsx?|jsx?)$/, '');
68
+ return relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
69
+ }
70
+ function toComponentName(pageId) {
71
+ return `NebulaPage_${pageId
72
+ .split(/[\/_-]/)
73
+ .filter(Boolean)
74
+ .map(segment => segment[0].toUpperCase() + segment.slice(1))
75
+ .join('')}`;
76
+ }
77
+ function resolveDevRunnerRoot(startDir) {
78
+ const candidates = [
79
+ path_1.default.join(startDir, 'packages', 'dev-runner'),
80
+ path_1.default.join(startDir, 'node_modules', '@nebula', 'dev-runner'),
81
+ ];
82
+ for (const candidate of candidates) {
83
+ if (fs_1.default.existsSync(path_1.default.join(candidate, 'package.json'))) {
84
+ return candidate;
85
+ }
86
+ }
87
+ let currentDir = startDir;
88
+ while (true) {
89
+ const candidate = path_1.default.join(currentDir, 'node_modules', '@nebula', 'dev-runner');
90
+ if (fs_1.default.existsSync(path_1.default.join(candidate, 'package.json'))) {
91
+ return candidate;
92
+ }
93
+ const parentDir = path_1.default.dirname(currentDir);
94
+ if (parentDir === currentDir) {
95
+ break;
96
+ }
97
+ currentDir = parentDir;
98
+ }
99
+ (0, utils_1.exitWithError)('Unable to locate @nebula-rn/dev-runner. Install it or use `nebula miniapp dev --no-runner`.');
100
+ }
101
+ function resolveRunnerPlatform(value) {
102
+ if (value === 'android' || value === 'ios') {
103
+ return value;
104
+ }
105
+ return process.platform === 'darwin' ? 'ios' : 'android';
106
+ }
107
+ function getMiniappServerBaseUrl(platform) {
108
+ return platform === 'android'
109
+ ? 'http://10.0.2.2:8082'
110
+ : 'http://localhost:8082';
111
+ }
112
+ function getRunnerLaunchUrl(platform, manifestPath, bundlePath) {
113
+ const baseUrl = getMiniappServerBaseUrl(platform);
114
+ const manifestUrl = `${baseUrl}${manifestPath}`;
115
+ const bundleUrl = `${baseUrl}${bundlePath}?platform=${platform}&dev=true&minify=false`;
116
+ return `devrunner://open?manifestUrl=${encodeURIComponent(manifestUrl)}&bundleUrl=${encodeURIComponent(bundleUrl)}&t=${Date.now()}`;
117
+ }
118
+ function launchRunnerControlUrl(platform, manifestPath, bundlePath) {
119
+ const runnerUrl = getRunnerLaunchUrl(platform, manifestPath, bundlePath);
120
+ if (platform === 'ios') {
121
+ (0, utils_1.spawnDetachedProcess)('xcrun', ['simctl', 'openurl', 'booted', runnerUrl], process.cwd());
122
+ return;
123
+ }
124
+ (0, utils_1.spawnDetachedProcess)('adb', [
125
+ 'shell',
126
+ 'am',
127
+ 'start',
128
+ '-a',
129
+ 'android.intent.action.VIEW',
130
+ '-d',
131
+ runnerUrl,
132
+ ], process.cwd());
133
+ }
134
+ function ensureDevRunner(workspaceRoot, platform, manifestPath, bundlePath) {
135
+ const devRunnerRoot = resolveDevRunnerRoot(workspaceRoot);
136
+ const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
137
+ (0, utils_1.spawnDetachedProcess)(npmCommand, ['run', 'start'], devRunnerRoot);
138
+ (0, utils_1.spawnDetachedProcess)(npmCommand, ['run', platform === 'ios' ? 'ios' : 'android'], devRunnerRoot);
139
+ const launchDelays = platform === 'ios' ? [6000, 12000] : [8000, 15000];
140
+ launchDelays.forEach(delay => {
141
+ setTimeout(() => {
142
+ launchRunnerControlUrl(platform, manifestPath, bundlePath);
143
+ }, delay);
144
+ });
145
+ }
146
+ function resolvePageModuleFile(projectRoot, pageId) {
147
+ const pageDir = path_1.default.join(projectRoot, 'src', 'pages', pageId);
148
+ const candidates = ['index.tsx', 'index.ts', 'index.jsx', 'index.js'];
149
+ for (const candidate of candidates) {
150
+ const filePath = path_1.default.join(pageDir, candidate);
151
+ if (fs_1.default.existsSync(filePath)) {
152
+ return filePath;
153
+ }
154
+ }
155
+ (0, utils_1.exitWithError)(`Missing page entry for "${pageId}" in ${pageDir}`);
156
+ }
157
+ function readPageConfig(projectRoot, pageId) {
158
+ registerTypeScriptRequire();
159
+ const pageConfigPath = path_1.default.join(projectRoot, 'src', 'pages', pageId, 'page.config.ts');
160
+ (0, utils_1.ensureFileExists)(pageConfigPath);
161
+ try {
162
+ const resolvedPath = require.resolve(pageConfigPath);
163
+ delete require.cache[resolvedPath];
164
+ const pageConfigModule = require(resolvedPath);
165
+ let pageConfig = null;
166
+ if (typeof pageConfigModule === 'object' &&
167
+ pageConfigModule !== null &&
168
+ ('default' in pageConfigModule || 'pageConfig' in pageConfigModule)) {
169
+ pageConfig =
170
+ pageConfigModule.default ?? pageConfigModule.pageConfig ?? null;
171
+ }
172
+ else if (typeof pageConfigModule === 'object' &&
173
+ pageConfigModule !== null) {
174
+ pageConfig = pageConfigModule;
175
+ }
176
+ if (!pageConfig || typeof pageConfig !== 'object') {
177
+ (0, utils_1.exitWithError)(`Invalid page config export in ${pageConfigPath}`);
178
+ }
179
+ return pageConfig;
180
+ }
181
+ catch (error) {
182
+ const message = error instanceof Error ? error.message : String(error);
183
+ (0, utils_1.exitWithError)(`Failed to load ${pageConfigPath}: ${message}`);
184
+ }
185
+ }
186
+ function validateMiniAppConfig(appConfig) {
187
+ if (!appConfig || typeof appConfig !== 'object') {
188
+ (0, utils_1.exitWithError)('app.json must export a JSON object');
189
+ }
190
+ if (!appConfig.appId || typeof appConfig.appId !== 'string') {
191
+ (0, utils_1.exitWithError)('app.json must contain a string "appId"');
192
+ }
193
+ if (!appConfig.pages ||
194
+ (typeof appConfig.pages !== 'object' && !Array.isArray(appConfig.pages))) {
195
+ (0, utils_1.exitWithError)('app.json must contain a "pages" array or object');
196
+ }
197
+ if (appConfig.updateStrategy &&
198
+ appConfig.updateStrategy !== 'auto' &&
199
+ appConfig.updateStrategy !== 'manual') {
200
+ (0, utils_1.exitWithError)('app.json updateStrategy must be either "auto" or "manual"');
201
+ }
202
+ }
203
+ function buildRuntimeManifest(projectRoot, appConfig) {
204
+ validateMiniAppConfig(appConfig);
205
+ const version = (0, utils_1.getPackageVersion)(projectRoot);
206
+ const updateStrategy = appConfig.updateStrategy ?? 'manual';
207
+ if (!Array.isArray(appConfig.pages)) {
208
+ const routes = appConfig.pages;
209
+ const entryPagePath = normalizeRoute(appConfig.entryPagePath || '/');
210
+ const pages = {};
211
+ for (const [route, componentName] of Object.entries(routes)) {
212
+ pages[normalizeRoute(route)] = componentName;
213
+ }
214
+ return {
215
+ manifest: {
216
+ appId: appConfig.appId,
217
+ bundlePath: '/index.bundle',
218
+ entryPagePath,
219
+ pageConfigs: {},
220
+ pages,
221
+ updateStrategy,
222
+ version,
223
+ window: appConfig.window,
224
+ },
225
+ pageDefinitions: [],
226
+ };
227
+ }
228
+ const pageDefinitions = [];
229
+ const pageConfigs = {};
230
+ const pages = {};
231
+ for (const pageId of appConfig.pages) {
232
+ const pageConfig = readPageConfig(projectRoot, pageId);
233
+ const route = normalizeRoute(pageConfig.route || pageId);
234
+ const componentName = toComponentName(pageId);
235
+ const moduleFilePath = resolvePageModuleFile(projectRoot, pageId);
236
+ const style = { ...pageConfig, route: undefined };
237
+ pages[route] = componentName;
238
+ pageConfigs[route] = style;
239
+ pageDefinitions.push({
240
+ componentName,
241
+ importPath: moduleFilePath,
242
+ });
243
+ }
244
+ const entryPagePath = normalizeRoute(appConfig.entryPagePath || appConfig.pages[0] || '/');
245
+ if (!pages[entryPagePath]) {
246
+ (0, utils_1.exitWithError)(`entryPagePath "${entryPagePath}" does not match any configured page`);
247
+ }
248
+ pages['/'] = pages[entryPagePath];
249
+ pageConfigs['/'] = pageConfigs[entryPagePath];
250
+ return {
251
+ manifest: {
252
+ appId: appConfig.appId,
253
+ bundlePath: '/index.bundle',
254
+ entryPagePath,
255
+ pageConfigs,
256
+ pages,
257
+ updateStrategy,
258
+ version,
259
+ window: appConfig.window,
260
+ },
261
+ pageDefinitions,
262
+ };
263
+ }
264
+ function generateMiniAppFiles(projectRoot, workspaceRoot, appConfig) {
265
+ const { manifest, pageDefinitions } = buildRuntimeManifest(projectRoot, appConfig);
266
+ const generatedDir = path_1.default.join(projectRoot, '.nebula', 'generated');
267
+ const runtimeBundlePath = '/.nebula/generated/index.bundle';
268
+ const runtimeBundleEntryFile = '.nebula/generated/index.js';
269
+ const runtimeManifestUrlPath = '/.nebula/generated/app.json';
270
+ manifest.bundlePath = runtimeBundlePath;
271
+ manifest.bundleEntryFile = runtimeBundleEntryFile;
272
+ const entryFilePath = path_1.default.join(generatedDir, 'index.js');
273
+ const metroConfigPath = path_1.default.join(generatedDir, 'metro.config.cjs');
274
+ const runtimeManifestPath = path_1.default.join(generatedDir, 'app.json');
275
+ const buildManifestPath = path_1.default.join(projectRoot, 'build', 'app.json');
276
+ fs_1.default.mkdirSync(generatedDir, { recursive: true });
277
+ const loaderEntries = pageDefinitions.map(definition => {
278
+ const importPath = normalizeImportPath(generatedDir, definition.importPath);
279
+ return ` ${JSON.stringify(definition.componentName)}: () => {
280
+ const module = require(${JSON.stringify(importPath)});
281
+ return module?.default ?? module;
282
+ },`;
283
+ });
284
+ const entryContents = `import { AppRegistry } from 'react-native';
285
+ import { createMiniAppPage, Miniapp, NebulaAPI } from '@nebula-rn/sdk';
286
+
287
+ const manifest = ${JSON.stringify(manifest, null, 2)};
288
+ const componentLoaders = {
289
+ ${loaderEntries.join('\n')}
290
+ };
291
+
292
+ Miniapp.bootstrap(manifest.appId);
293
+ NebulaAPI.registerManifest(manifest.appId, manifest);
294
+
295
+ Object.entries(manifest.pages).forEach(([routePath, componentName]) => {
296
+ const loadComponent = componentLoaders[componentName];
297
+ if (!loadComponent) {
298
+ console.warn(\`[Nebula] No component found for route "\${routePath}" and component "\${componentName}"\`);
299
+ return;
300
+ }
301
+ AppRegistry.registerComponent(componentName, () => {
302
+ const component = loadComponent();
303
+ return createMiniAppPage(component);
304
+ });
305
+ });
306
+
307
+ const defaultComponentName =
308
+ manifest.pages[manifest.entryPagePath] ||
309
+ manifest.pages['/'] ||
310
+ Object.values(manifest.pages)[0];
311
+ const loadDefaultComponent = defaultComponentName
312
+ ? componentLoaders[defaultComponentName]
313
+ : null;
314
+
315
+ if (loadDefaultComponent) {
316
+ AppRegistry.registerComponent('NebulaApp', () => {
317
+ const component = loadDefaultComponent();
318
+ return createMiniAppPage(component);
319
+ });
320
+ }
321
+ `;
322
+ const metroConfigContents = `const fs = require('fs');
323
+ const path = require('path');
324
+ const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
325
+
326
+ const projectRoot = ${JSON.stringify(projectRoot)};
327
+ const workspaceRoot = ${JSON.stringify(workspaceRoot)};
328
+ const generatedManifestPath = ${JSON.stringify(runtimeManifestPath)};
329
+ const runtimeManifestUrlPath = ${JSON.stringify(runtimeManifestUrlPath)};
330
+
331
+ /** @type {import('metro-config').MetroConfig} */
332
+ const config = {
333
+ projectRoot,
334
+ watchFolders: [workspaceRoot],
335
+ server: {
336
+ enhanceMiddleware: middleware => {
337
+ return (req, res, next) => {
338
+ const requestUrl = new URL(req.url || '/', 'http://localhost');
339
+ if (
340
+ requestUrl.pathname === runtimeManifestUrlPath &&
341
+ fs.existsSync(generatedManifestPath)
342
+ ) {
343
+ res.setHeader('Content-Type', 'application/json');
344
+ res.end(fs.readFileSync(generatedManifestPath, 'utf8'));
345
+ return;
346
+ }
347
+ return middleware(req, res, next);
348
+ };
349
+ },
350
+ },
351
+ resolver: {
352
+ nodeModulesPaths: [
353
+ path.join(projectRoot, 'node_modules'),
354
+ path.join(workspaceRoot, 'node_modules'),
355
+ ],
356
+ },
357
+ };
358
+
359
+ module.exports = mergeConfig(getDefaultConfig(projectRoot), config);
360
+ `;
361
+ (0, utils_1.writeFileIfChanged)(entryFilePath, entryContents);
362
+ (0, utils_1.writeFileIfChanged)(metroConfigPath, metroConfigContents);
363
+ (0, utils_1.writeFileIfChanged)(runtimeManifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
364
+ return {
365
+ buildManifestPath,
366
+ bundlePath: runtimeBundlePath,
367
+ bundleEntryFile: runtimeBundleEntryFile,
368
+ entryFilePath,
369
+ metroConfigPath,
370
+ runtimeManifestUrlPath,
371
+ };
372
+ }
373
+ async function runReactNative(projectRoot, args) {
374
+ const reactNativeBin = (0, utils_1.resolveBin)('react-native', projectRoot);
375
+ await (0, utils_1.spawnProcess)(projectRoot, reactNativeBin, args);
376
+ }
377
+ async function buildMiniAppArtifacts(projectRoot, workspaceRoot, appConfig, extraArgs) {
378
+ const generatedFiles = generateMiniAppFiles(projectRoot, workspaceRoot, appConfig);
379
+ const buildDir = path_1.default.join(projectRoot, 'build');
380
+ const iosBundlePath = path_1.default.join(buildDir, 'main.ios.jsbundle');
381
+ const androidBundlePath = path_1.default.join(buildDir, 'main.android.bundle');
382
+ fs_1.default.mkdirSync(buildDir, { recursive: true });
383
+ fs_1.default.copyFileSync(path_1.default.join(path_1.default.dirname(generatedFiles.entryFilePath), 'app.json'), generatedFiles.buildManifestPath);
384
+ const iosAssetsDir = path_1.default.join(buildDir, 'ios-assets');
385
+ const androidAssetsDir = path_1.default.join(buildDir, 'android-assets');
386
+ await Promise.all([
387
+ runReactNative(projectRoot, [
388
+ 'bundle',
389
+ '--config',
390
+ generatedFiles.metroConfigPath,
391
+ '--platform',
392
+ 'ios',
393
+ '--dev',
394
+ 'false',
395
+ '--entry-file',
396
+ generatedFiles.entryFilePath,
397
+ '--bundle-output',
398
+ iosBundlePath,
399
+ '--assets-dest',
400
+ iosAssetsDir,
401
+ ...extraArgs,
402
+ ]),
403
+ runReactNative(projectRoot, [
404
+ 'bundle',
405
+ '--config',
406
+ generatedFiles.metroConfigPath,
407
+ '--platform',
408
+ 'android',
409
+ '--dev',
410
+ 'false',
411
+ '--entry-file',
412
+ generatedFiles.entryFilePath,
413
+ '--bundle-output',
414
+ androidBundlePath,
415
+ '--assets-dest',
416
+ androidAssetsDir,
417
+ ...extraArgs,
418
+ ]),
419
+ ]);
420
+ return {
421
+ androidBundlePath,
422
+ iosBundlePath,
423
+ iosAssetsDir,
424
+ androidAssetsDir,
425
+ manifestPath: generatedFiles.buildManifestPath,
426
+ };
427
+ }
428
+ async function runMiniApp(action, extraArgs) {
429
+ const projectRoot = process.cwd();
430
+ const workspaceRoot = (0, utils_1.findWorkspaceRoot)(projectRoot);
431
+ const appJsonPath = path_1.default.join(projectRoot, 'app.json');
432
+ const packageJsonPath = path_1.default.join(projectRoot, 'package.json');
433
+ (0, utils_1.ensureFileExists)(appJsonPath);
434
+ (0, utils_1.ensureFileExists)(packageJsonPath);
435
+ const appConfig = JSON.parse(fs_1.default.readFileSync(appJsonPath, 'utf8'));
436
+ const generatedFiles = generateMiniAppFiles(projectRoot, workspaceRoot, appConfig);
437
+ if (action === 'dev') {
438
+ const flags = (0, utils_1.parseFlags)(extraArgs);
439
+ const runnerPlatform = resolveRunnerPlatform(flags.platform);
440
+ const shouldAutoLaunchRunner = flags['no-runner'] !== 'true';
441
+ const metroArgs = (0, utils_1.omitFlags)(extraArgs, ['platform', 'no-runner']);
442
+ console.log(`[nebula] Starting ${appConfig.appId} in development mode...`);
443
+ if (shouldAutoLaunchRunner) {
444
+ console.log(`[nebula] Opening ${appConfig.appId} on ${runnerPlatform}...`);
445
+ ensureDevRunner(workspaceRoot, runnerPlatform, generatedFiles.runtimeManifestUrlPath, generatedFiles.bundlePath);
446
+ }
447
+ else {
448
+ console.log('[nebula] Runner auto-launch is disabled. Only the miniapp dev server will be started.');
449
+ }
450
+ await runReactNative(projectRoot, [
451
+ 'start',
452
+ '--config',
453
+ generatedFiles.metroConfigPath,
454
+ '--port',
455
+ '8082',
456
+ ...metroArgs,
457
+ ]);
458
+ return;
459
+ }
460
+ if (action === 'build') {
461
+ await buildMiniAppArtifacts(projectRoot, workspaceRoot, appConfig, extraArgs);
462
+ return;
463
+ }
464
+ (0, utils_1.exitWithError)(`Unsupported miniapp action "${action}". Use "dev" / "build" / "upload".`);
465
+ }
@@ -0,0 +1,4 @@
1
+ export declare function loginPlatform(extraArgs: string[]): Promise<void>;
2
+ export declare function logoutPlatform(): void;
3
+ export declare function configureServer(extraArgs: string[]): void;
4
+ export declare function uploadMiniApp(extraArgs: string[]): Promise<void>;
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loginPlatform = loginPlatform;
7
+ exports.logoutPlatform = logoutPlatform;
8
+ exports.configureServer = configureServer;
9
+ exports.uploadMiniApp = uploadMiniApp;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const utils_1 = require("./utils");
13
+ const miniapp_1 = require("./miniapp");
14
+ async function ensureAuthenticatedSession(serverUrl, accessToken) {
15
+ const response = await fetch(`${serverUrl.replace(/\/$/, '')}/auth/me`, {
16
+ headers: {
17
+ Authorization: `Bearer ${accessToken}`,
18
+ },
19
+ method: 'GET',
20
+ });
21
+ if (response.status === 401 || response.status === 403) {
22
+ const currentConfig = (0, utils_1.readNebulaConfig)();
23
+ (0, utils_1.writeNebulaConfig)({
24
+ ...currentConfig,
25
+ accessToken: undefined,
26
+ user: undefined,
27
+ });
28
+ (0, utils_1.exitWithError)('Saved CLI login is no longer valid. Please run `nebula auth login` again.');
29
+ }
30
+ if (!response.ok) {
31
+ const text = await response.text();
32
+ (0, utils_1.exitWithError)(text || `Failed to validate CLI login (status ${response.status})`);
33
+ }
34
+ return (await response.json());
35
+ }
36
+ async function loginPlatform(extraArgs) {
37
+ const flags = (0, utils_1.parseFlags)(extraArgs);
38
+ const config = (0, utils_1.readNebulaConfig)();
39
+ const serverUrl = (0, utils_1.normalizeServerUrl)(flags.server || config.serverUrl || 'http://localhost:3001/api');
40
+ const shouldOpen = flags.open !== 'false';
41
+ const session = await (0, utils_1.httpRequest)(`${serverUrl.replace(/\/$/, '')}/auth/cli/sessions`, {
42
+ method: 'POST',
43
+ });
44
+ console.log('[nebula] Open this URL in your browser to authorize the CLI:');
45
+ console.log(session.verificationUrl);
46
+ console.log(`[nebula] Session code: ${session.code}`);
47
+ if (shouldOpen) {
48
+ try {
49
+ (0, utils_1.tryOpenUrl)(session.verificationUrl);
50
+ }
51
+ catch {
52
+ // Ignore open failures and keep the manual URL flow.
53
+ }
54
+ }
55
+ const expiresAt = new Date(session.expiresAt).getTime();
56
+ while (Date.now() < expiresAt) {
57
+ const response = await (0, utils_1.httpRequest)(`${serverUrl.replace(/\/$/, '')}/auth/cli/sessions/${session.code}`, {
58
+ method: 'GET',
59
+ });
60
+ if (response.status === 'approved' &&
61
+ response.accessToken &&
62
+ response.user) {
63
+ (0, utils_1.writeNebulaConfig)({
64
+ accessToken: response.accessToken,
65
+ serverUrl,
66
+ user: response.user,
67
+ });
68
+ console.log(`[nebula] Logged in as ${response.user.displayName} <${response.user.email}>`);
69
+ return;
70
+ }
71
+ if (response.status === 'expired') {
72
+ (0, utils_1.exitWithError)('CLI login session expired. Please run `nebula auth login` again.');
73
+ }
74
+ await (0, utils_1.sleep)((session.intervalSeconds || 2) * 1000);
75
+ }
76
+ (0, utils_1.exitWithError)('CLI login session expired. Please run `nebula auth login` again.');
77
+ }
78
+ function logoutPlatform() {
79
+ (0, utils_1.writeNebulaConfig)({});
80
+ console.log('[nebula] Logged out');
81
+ }
82
+ function configureServer(extraArgs) {
83
+ const flags = (0, utils_1.parseFlags)(extraArgs);
84
+ const currentConfig = (0, utils_1.readNebulaConfig)();
85
+ const requestedUrl = flags.url || extraArgs.find(arg => !arg.startsWith('--')) || '';
86
+ if (flags.clear === 'true') {
87
+ (0, utils_1.writeNebulaConfig)({
88
+ accessToken: undefined,
89
+ serverUrl: undefined,
90
+ user: undefined,
91
+ });
92
+ console.log('[nebula] Cleared saved server configuration');
93
+ return;
94
+ }
95
+ if (!requestedUrl) {
96
+ if (currentConfig.serverUrl) {
97
+ console.log(`[nebula] Current server: ${currentConfig.serverUrl}`);
98
+ }
99
+ else {
100
+ console.log('[nebula] No server configured');
101
+ }
102
+ return;
103
+ }
104
+ const nextServerUrl = (0, utils_1.normalizeServerUrl)(requestedUrl);
105
+ const serverChanged = currentConfig.serverUrl !== nextServerUrl;
106
+ (0, utils_1.writeNebulaConfig)({
107
+ ...currentConfig,
108
+ accessToken: serverChanged ? undefined : currentConfig.accessToken,
109
+ serverUrl: nextServerUrl,
110
+ user: serverChanged ? undefined : currentConfig.user,
111
+ });
112
+ console.log(`[nebula] Saved server: ${nextServerUrl}`);
113
+ if (serverChanged && currentConfig.accessToken) {
114
+ console.log('[nebula] Cleared saved login because the server changed');
115
+ }
116
+ }
117
+ async function zipDirectory(dirPath, outputPath) {
118
+ const ZipArchive = require('archiver').ZipArchive;
119
+ return new Promise((resolve, reject) => {
120
+ const output = fs_1.default.createWriteStream(outputPath);
121
+ const archive = new ZipArchive({ zlib: { level: 9 } });
122
+ output.on('close', resolve);
123
+ archive.on('error', reject);
124
+ archive.pipe(output);
125
+ archive.directory(dirPath, false);
126
+ archive.finalize();
127
+ });
128
+ }
129
+ async function uploadMiniApp(extraArgs) {
130
+ const projectRoot = process.cwd();
131
+ const workspaceRoot = (0, utils_1.findWorkspaceRoot)(projectRoot);
132
+ const flags = (0, utils_1.parseFlags)(extraArgs);
133
+ const config = (0, utils_1.readNebulaConfig)();
134
+ const serverUrl = flags.server
135
+ ? (0, utils_1.normalizeServerUrl)(flags.server)
136
+ : config.serverUrl;
137
+ const accessToken = config.accessToken;
138
+ if (!serverUrl || !accessToken) {
139
+ (0, utils_1.exitWithError)('Please configure a server and login first: nebula config server <url> && nebula auth login');
140
+ }
141
+ const currentUser = await ensureAuthenticatedSession(serverUrl, accessToken);
142
+ console.log(`[nebula] Authenticated as ${currentUser.displayName} <${currentUser.email}>`);
143
+ const appJsonPath = path_1.default.join(projectRoot, 'app.json');
144
+ (0, utils_1.ensureFileExists)(appJsonPath);
145
+ const appConfig = (0, utils_1.readJson)(appJsonPath);
146
+ const buildArgs = (0, utils_1.omitFlags)(extraArgs, [
147
+ 'server',
148
+ 'bundle',
149
+ 'bundle-ios',
150
+ 'bundle-android',
151
+ 'manifest',
152
+ 'version',
153
+ 'build-number',
154
+ 'changelog',
155
+ 'channel',
156
+ 'release-type',
157
+ ]);
158
+ const builtArtifacts = await (0, miniapp_1.buildMiniAppArtifacts)(projectRoot, workspaceRoot, appConfig, buildArgs);
159
+ const manifestPath = builtArtifacts.manifestPath;
160
+ const iosBundlePath = builtArtifacts.iosBundlePath;
161
+ const androidBundlePath = builtArtifacts.androidBundlePath;
162
+ const iosAssetsDir = builtArtifacts.iosAssetsDir;
163
+ const androidAssetsDir = builtArtifacts.androidAssetsDir;
164
+ (0, utils_1.ensureFileExists)(iosBundlePath);
165
+ (0, utils_1.ensureFileExists)(androidBundlePath);
166
+ (0, utils_1.ensureFileExists)(manifestPath);
167
+ const buildDir = path_1.default.join(projectRoot, 'build');
168
+ const iosAssetsZipPath = path_1.default.join(buildDir, 'assets-ios.zip');
169
+ const androidAssetsZipPath = path_1.default.join(buildDir, 'assets-android.zip');
170
+ await zipDirectory(iosAssetsDir, iosAssetsZipPath);
171
+ await zipDirectory(androidAssetsDir, androidAssetsZipPath);
172
+ (0, utils_1.ensureFileExists)(iosAssetsZipPath);
173
+ (0, utils_1.ensureFileExists)(androidAssetsZipPath);
174
+ const version = flags.version || (0, utils_1.getPackageVersion)(projectRoot);
175
+ const changelog = flags.changelog || '';
176
+ const formData = new FormData();
177
+ formData.append('version', version);
178
+ formData.append('changelog', changelog);
179
+ formData.append('channel', 'stable');
180
+ formData.append('releaseType', 'RELEASE');
181
+ formData.append('bundleIos', new Blob([fs_1.default.readFileSync(iosBundlePath)]), path_1.default.basename(iosBundlePath));
182
+ formData.append('bundleAndroid', new Blob([fs_1.default.readFileSync(androidBundlePath)]), path_1.default.basename(androidBundlePath));
183
+ formData.append('manifest', new Blob([fs_1.default.readFileSync(manifestPath)]), path_1.default.basename(manifestPath));
184
+ formData.append('assetsIos', new Blob([fs_1.default.readFileSync(iosAssetsZipPath)]), 'assets-ios.zip');
185
+ formData.append('assetsAndroid', new Blob([fs_1.default.readFileSync(androidAssetsZipPath)]), 'assets-android.zip');
186
+ (0, utils_1.writeNebulaConfig)({
187
+ ...config,
188
+ serverUrl,
189
+ });
190
+ const response = await (0, utils_1.httpRequest)(`${serverUrl.replace(/\/$/, '')}/mini-apps/by-app-id/${appConfig.appId}/versions/upload`, {
191
+ body: formData,
192
+ headers: {
193
+ Authorization: `Bearer ${accessToken}`,
194
+ },
195
+ method: 'POST',
196
+ });
197
+ console.log(`[nebula] Uploaded ${appConfig.appId}@${response.version} status=${response.status}`);
198
+ }
@@ -0,0 +1,27 @@
1
+ export type NebulaPlatformConfig = {
2
+ accessToken?: string;
3
+ serverUrl?: string;
4
+ user?: {
5
+ id: string;
6
+ email: string;
7
+ displayName: string;
8
+ };
9
+ };
10
+ export declare function exitWithError(message: string): never;
11
+ export declare function ensureFileExists(filePath: string): void;
12
+ export declare function readJson<T>(filePath: string): T;
13
+ export declare function writeFileIfChanged(filePath: string, contents: string): void;
14
+ export declare function resolveBin(binName: string, cwd: string): string;
15
+ export declare function findWorkspaceRoot(startDir: string): string;
16
+ export declare function parseFlags(args: string[]): Record<string, string>;
17
+ export declare function omitFlags(args: string[], excludedKeys: string[]): string[];
18
+ export declare function getPackageVersion(projectRoot: string): string;
19
+ export declare function spawnProcess(projectRoot: string, command: string, args: string[]): Promise<void>;
20
+ export declare function httpRequest<T>(input: string, init: RequestInit): Promise<T>;
21
+ export declare function sleep(ms: number): Promise<void>;
22
+ export declare function tryOpenUrl(url: string): void;
23
+ export declare function spawnDetachedProcess(command: string, args: string[], cwd: string): void;
24
+ export declare function getNebulaConfigPath(): string;
25
+ export declare function readNebulaConfig(): NebulaPlatformConfig;
26
+ export declare function writeNebulaConfig(config: NebulaPlatformConfig): void;
27
+ export declare function normalizeServerUrl(rawValue: string): string;
package/dist/utils.js ADDED
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.exitWithError = exitWithError;
7
+ exports.ensureFileExists = ensureFileExists;
8
+ exports.readJson = readJson;
9
+ exports.writeFileIfChanged = writeFileIfChanged;
10
+ exports.resolveBin = resolveBin;
11
+ exports.findWorkspaceRoot = findWorkspaceRoot;
12
+ exports.parseFlags = parseFlags;
13
+ exports.omitFlags = omitFlags;
14
+ exports.getPackageVersion = getPackageVersion;
15
+ exports.spawnProcess = spawnProcess;
16
+ exports.httpRequest = httpRequest;
17
+ exports.sleep = sleep;
18
+ exports.tryOpenUrl = tryOpenUrl;
19
+ exports.spawnDetachedProcess = spawnDetachedProcess;
20
+ exports.getNebulaConfigPath = getNebulaConfigPath;
21
+ exports.readNebulaConfig = readNebulaConfig;
22
+ exports.writeNebulaConfig = writeNebulaConfig;
23
+ exports.normalizeServerUrl = normalizeServerUrl;
24
+ const fs_1 = __importDefault(require("fs"));
25
+ const os_1 = __importDefault(require("os"));
26
+ const path_1 = __importDefault(require("path"));
27
+ const child_process_1 = require("child_process");
28
+ function exitWithError(message) {
29
+ console.error(`[nebula] ${message}`);
30
+ process.exit(1);
31
+ }
32
+ function ensureFileExists(filePath) {
33
+ if (!fs_1.default.existsSync(filePath)) {
34
+ exitWithError(`Missing required file: ${filePath}`);
35
+ }
36
+ }
37
+ function readJson(filePath) {
38
+ try {
39
+ return JSON.parse(fs_1.default.readFileSync(filePath, 'utf8'));
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ exitWithError(`Failed to parse ${filePath}: ${message}`);
44
+ }
45
+ }
46
+ function writeFileIfChanged(filePath, contents) {
47
+ const previousContents = fs_1.default.existsSync(filePath)
48
+ ? fs_1.default.readFileSync(filePath, 'utf8')
49
+ : null;
50
+ if (previousContents !== contents) {
51
+ fs_1.default.writeFileSync(filePath, contents);
52
+ }
53
+ }
54
+ function resolveBin(binName, cwd) {
55
+ const extensions = process.platform === 'win32' ? ['.cmd', '.exe', ''] : [''];
56
+ let currentDir = cwd;
57
+ while (true) {
58
+ for (const extension of extensions) {
59
+ const candidate = path_1.default.join(currentDir, 'node_modules', '.bin', `${binName}${extension}`);
60
+ if (fs_1.default.existsSync(candidate)) {
61
+ return candidate;
62
+ }
63
+ }
64
+ const parentDir = path_1.default.dirname(currentDir);
65
+ if (parentDir === currentDir) {
66
+ break;
67
+ }
68
+ currentDir = parentDir;
69
+ }
70
+ exitWithError(`Unable to find "${binName}" from ${cwd}`);
71
+ }
72
+ function findWorkspaceRoot(startDir) {
73
+ let currentDir = startDir;
74
+ while (true) {
75
+ const packageJsonPath = path_1.default.join(currentDir, 'package.json');
76
+ if (fs_1.default.existsSync(packageJsonPath)) {
77
+ const packageJson = readJson(packageJsonPath);
78
+ if (packageJson.workspaces) {
79
+ return currentDir;
80
+ }
81
+ }
82
+ const parentDir = path_1.default.dirname(currentDir);
83
+ if (parentDir === currentDir) {
84
+ break;
85
+ }
86
+ currentDir = parentDir;
87
+ }
88
+ exitWithError(`Unable to locate workspace root from ${startDir}`);
89
+ }
90
+ function parseFlags(args) {
91
+ const flags = {};
92
+ for (let index = 0; index < args.length; index += 1) {
93
+ const current = args[index];
94
+ if (!current.startsWith('--')) {
95
+ continue;
96
+ }
97
+ const key = current.slice(2);
98
+ const next = args[index + 1];
99
+ if (!next || next.startsWith('--')) {
100
+ flags[key] = 'true';
101
+ continue;
102
+ }
103
+ flags[key] = next;
104
+ index += 1;
105
+ }
106
+ return flags;
107
+ }
108
+ function omitFlags(args, excludedKeys) {
109
+ const excluded = new Set(excludedKeys);
110
+ const result = [];
111
+ for (let index = 0; index < args.length; index += 1) {
112
+ const current = args[index];
113
+ if (!current.startsWith('--')) {
114
+ result.push(current);
115
+ continue;
116
+ }
117
+ const key = current.slice(2);
118
+ if (!excluded.has(key)) {
119
+ result.push(current);
120
+ const next = args[index + 1];
121
+ if (next && !next.startsWith('--')) {
122
+ result.push(next);
123
+ index += 1;
124
+ }
125
+ continue;
126
+ }
127
+ const next = args[index + 1];
128
+ if (next && !next.startsWith('--')) {
129
+ index += 1;
130
+ }
131
+ }
132
+ return result;
133
+ }
134
+ function getPackageVersion(projectRoot) {
135
+ const packageJson = readJson(path_1.default.join(projectRoot, 'package.json'));
136
+ return packageJson.version || '0.0.1';
137
+ }
138
+ function spawnProcess(projectRoot, command, args) {
139
+ return new Promise((resolve, reject) => {
140
+ const child = (0, child_process_1.spawn)(command, args, {
141
+ cwd: projectRoot,
142
+ stdio: 'inherit',
143
+ });
144
+ child.on('error', reject);
145
+ child.on('exit', (code, signal) => {
146
+ if (signal) {
147
+ reject(new Error(`Process terminated with signal ${signal}`));
148
+ return;
149
+ }
150
+ if (code && code !== 0) {
151
+ reject(new Error(`Process exited with code ${code}`));
152
+ return;
153
+ }
154
+ resolve();
155
+ });
156
+ });
157
+ }
158
+ async function httpRequest(input, init) {
159
+ const response = await fetch(input, init);
160
+ if (!response.ok) {
161
+ const text = await response.text();
162
+ exitWithError(text || `Request failed with status ${response.status}`);
163
+ }
164
+ return (await response.json());
165
+ }
166
+ function sleep(ms) {
167
+ return new Promise(resolve => {
168
+ setTimeout(resolve, ms);
169
+ });
170
+ }
171
+ function tryOpenUrl(url) {
172
+ const command = process.platform === 'darwin'
173
+ ? 'open'
174
+ : process.platform === 'win32'
175
+ ? 'cmd'
176
+ : 'xdg-open';
177
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
178
+ const child = (0, child_process_1.spawn)(command, args, {
179
+ detached: true,
180
+ stdio: 'ignore',
181
+ });
182
+ child.unref();
183
+ }
184
+ function spawnDetachedProcess(command, args, cwd) {
185
+ const child = (0, child_process_1.spawn)(command, args, {
186
+ cwd,
187
+ detached: true,
188
+ stdio: 'ignore',
189
+ });
190
+ child.unref();
191
+ }
192
+ function getNebulaConfigPath() {
193
+ return path_1.default.join(os_1.default.homedir(), '.nebula', 'config.json');
194
+ }
195
+ function readNebulaConfig() {
196
+ const configPath = getNebulaConfigPath();
197
+ if (!fs_1.default.existsSync(configPath)) {
198
+ return {};
199
+ }
200
+ return readJson(configPath);
201
+ }
202
+ function writeNebulaConfig(config) {
203
+ const configPath = getNebulaConfigPath();
204
+ fs_1.default.mkdirSync(path_1.default.dirname(configPath), { recursive: true });
205
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
206
+ }
207
+ function normalizeServerUrl(rawValue) {
208
+ const value = rawValue.trim();
209
+ if (!value) {
210
+ exitWithError('Server URL cannot be empty.');
211
+ }
212
+ return value.replace(/\/$/, '');
213
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@nebula-rn/cli",
3
+ "version": "0.0.1",
4
+ "description": "Nebula CLI - build, bundle and upload mini-apps",
5
+ "author": "Hector Zhuang",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Hector-Zhuang/nebula.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/Hector-Zhuang/nebula/issues"
14
+ },
15
+ "homepage": "https://github.com/Hector-Zhuang/nebula/tree/main/packages/cli#readme",
16
+ "keywords": ["nebula", "superapp", "miniapp", "cli"],
17
+ "main": "./dist/cli.js",
18
+ "types": "./dist/cli.d.ts",
19
+ "bin": {
20
+ "nebula": "./dist/cli.js"
21
+ },
22
+ "files": ["dist", "bin", "README.md"],
23
+ "publishConfig": { "access": "public" },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "prepack": "npm run build"
27
+ },
28
+ "dependencies": {
29
+ "archiver": "^8.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/archiver": "^8.0.0"
33
+ }
34
+ }