happy-stacks 0.3.0 → 0.5.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/README.md +93 -40
- package/bin/happys.mjs +158 -16
- package/docs/codex-mcp-resume.md +130 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-analysis.md +17640 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-export.fuller-stat.md +3845 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-inventory.md +102 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-manual-review.md +1452 -0
- package/docs/commit-audits/happy/leeroy-wip.manual-review-queue.md +116 -0
- package/docs/happy-development.md +3 -4
- package/docs/isolated-linux-vm.md +82 -0
- package/docs/mobile-ios.md +112 -54
- package/docs/monorepo-migration.md +286 -0
- package/docs/server-flavors.md +19 -3
- package/docs/stacks.md +35 -0
- package/package.json +5 -1
- package/scripts/auth.mjs +32 -10
- package/scripts/build.mjs +55 -8
- package/scripts/daemon.mjs +166 -10
- package/scripts/dev.mjs +198 -50
- package/scripts/doctor.mjs +0 -4
- package/scripts/edison.mjs +6 -4
- package/scripts/env.mjs +150 -0
- package/scripts/env_cmd.test.mjs +128 -0
- package/scripts/init.mjs +8 -3
- package/scripts/install.mjs +207 -69
- package/scripts/lint.mjs +24 -4
- package/scripts/migrate.mjs +3 -12
- package/scripts/mobile.mjs +88 -104
- package/scripts/mobile_dev_client.mjs +83 -0
- package/scripts/monorepo.mjs +1096 -0
- package/scripts/monorepo_port.test.mjs +1470 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +51 -0
- package/scripts/review.mjs +908 -0
- package/scripts/review_pr.mjs +353 -0
- package/scripts/run.mjs +101 -21
- package/scripts/service.mjs +2 -2
- package/scripts/setup.mjs +189 -68
- package/scripts/setup_pr.mjs +586 -38
- package/scripts/stack.mjs +990 -196
- package/scripts/stack_archive_cmd.test.mjs +91 -0
- package/scripts/stack_editor_workspace_monorepo_root.test.mjs +65 -0
- package/scripts/stack_env_cmd.test.mjs +87 -0
- package/scripts/stack_happy_cmd.test.mjs +126 -0
- package/scripts/stack_interactive_monorepo_group.test.mjs +71 -0
- package/scripts/stack_monorepo_defaults.test.mjs +62 -0
- package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +66 -0
- package/scripts/stack_server_flavors_defaults.test.mjs +55 -0
- package/scripts/stack_shorthand_cmd.test.mjs +55 -0
- package/scripts/stack_wt_list.test.mjs +128 -0
- package/scripts/tailscale.mjs +37 -1
- package/scripts/test.mjs +45 -8
- package/scripts/tui.mjs +395 -39
- package/scripts/typecheck.mjs +24 -4
- package/scripts/utils/auth/daemon_gate.mjs +55 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +37 -0
- package/scripts/utils/auth/guided_pr_auth.mjs +79 -0
- package/scripts/utils/auth/guided_stack_web_login.mjs +75 -0
- package/scripts/utils/auth/interactive_stack_auth.mjs +72 -0
- package/scripts/utils/auth/login_ux.mjs +32 -13
- package/scripts/utils/auth/sources.mjs +26 -0
- package/scripts/utils/auth/stack_guided_login.mjs +353 -0
- package/scripts/utils/cli/cli_registry.mjs +43 -4
- package/scripts/utils/cli/cwd_scope.mjs +136 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +110 -0
- package/scripts/utils/cli/log_forwarder.mjs +157 -0
- package/scripts/utils/cli/prereqs.mjs +75 -0
- package/scripts/utils/cli/prereqs.test.mjs +34 -0
- package/scripts/utils/cli/progress.mjs +126 -0
- package/scripts/utils/cli/verbosity.mjs +12 -0
- package/scripts/utils/cli/wizard.mjs +17 -9
- package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +60 -0
- package/scripts/utils/dev/daemon.mjs +61 -4
- package/scripts/utils/dev/expo_dev.mjs +430 -0
- package/scripts/utils/dev/expo_dev.test.mjs +76 -0
- package/scripts/utils/dev/server.mjs +36 -42
- package/scripts/utils/dev_auth_key.mjs +169 -0
- package/scripts/utils/edison/git_roots.mjs +29 -0
- package/scripts/utils/edison/git_roots.test.mjs +36 -0
- package/scripts/utils/env/env.mjs +7 -3
- package/scripts/utils/env/env_file.mjs +4 -2
- package/scripts/utils/env/env_file.test.mjs +44 -0
- package/scripts/utils/expo/command.mjs +52 -0
- package/scripts/utils/expo/expo.mjs +20 -1
- package/scripts/utils/expo/metro_ports.mjs +114 -0
- package/scripts/utils/git/git.mjs +67 -0
- package/scripts/utils/git/worktrees.mjs +80 -25
- package/scripts/utils/git/worktrees_monorepo.test.mjs +54 -0
- package/scripts/utils/handy_master_secret.mjs +94 -0
- package/scripts/utils/mobile/config.mjs +31 -0
- package/scripts/utils/mobile/dev_client_links.mjs +60 -0
- package/scripts/utils/mobile/identifiers.mjs +47 -0
- package/scripts/utils/mobile/identifiers.test.mjs +42 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +98 -0
- package/scripts/utils/net/lan_ip.mjs +24 -0
- package/scripts/utils/net/ports.mjs +9 -1
- package/scripts/utils/net/tcp_forward.mjs +162 -0
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +20 -0
- package/scripts/utils/paths/localhost_host.mjs +50 -3
- package/scripts/utils/paths/paths.mjs +159 -40
- package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
- package/scripts/utils/paths/paths_server_flavors.test.mjs +45 -0
- package/scripts/utils/proc/commands.mjs +2 -3
- package/scripts/utils/proc/parallel.mjs +25 -0
- package/scripts/utils/proc/pm.mjs +176 -22
- package/scripts/utils/proc/pm_spawn.test.mjs +76 -0
- package/scripts/utils/proc/pm_stack_cache_env.test.mjs +142 -0
- package/scripts/utils/proc/proc.mjs +136 -4
- package/scripts/utils/proc/proc.test.mjs +77 -0
- package/scripts/utils/review/base_ref.mjs +74 -0
- package/scripts/utils/review/base_ref.test.mjs +54 -0
- package/scripts/utils/review/chunks.mjs +55 -0
- package/scripts/utils/review/chunks.test.mjs +51 -0
- package/scripts/utils/review/findings.mjs +165 -0
- package/scripts/utils/review/findings.test.mjs +85 -0
- package/scripts/utils/review/head_slice.mjs +153 -0
- package/scripts/utils/review/head_slice.test.mjs +91 -0
- package/scripts/utils/review/instructions/deep.md +20 -0
- package/scripts/utils/review/runners/coderabbit.mjs +61 -0
- package/scripts/utils/review/runners/coderabbit.test.mjs +59 -0
- package/scripts/utils/review/runners/codex.mjs +61 -0
- package/scripts/utils/review/runners/codex.test.mjs +35 -0
- package/scripts/utils/review/slices.mjs +140 -0
- package/scripts/utils/review/slices.test.mjs +32 -0
- package/scripts/utils/review/targets.mjs +24 -0
- package/scripts/utils/review/targets.test.mjs +36 -0
- package/scripts/utils/sandbox/review_pr_sandbox.mjs +106 -0
- package/scripts/utils/server/flavor_scripts.mjs +98 -0
- package/scripts/utils/server/flavor_scripts.test.mjs +146 -0
- package/scripts/utils/server/mobile_api_url.mjs +61 -0
- package/scripts/utils/server/mobile_api_url.test.mjs +41 -0
- package/scripts/utils/server/prisma_import.mjs +37 -0
- package/scripts/utils/server/prisma_import.test.mjs +70 -0
- package/scripts/utils/server/ui_env.mjs +14 -0
- package/scripts/utils/server/ui_env.test.mjs +46 -0
- package/scripts/utils/server/urls.mjs +14 -4
- package/scripts/utils/server/validate.mjs +53 -16
- package/scripts/utils/server/validate.test.mjs +89 -0
- package/scripts/utils/service/autostart_darwin.mjs +42 -2
- package/scripts/utils/service/autostart_darwin.test.mjs +50 -0
- package/scripts/utils/stack/context.mjs +2 -2
- package/scripts/utils/stack/editor_workspace.mjs +6 -6
- package/scripts/utils/stack/interactive_stack_config.mjs +185 -0
- package/scripts/utils/stack/pr_stack_name.mjs +16 -0
- package/scripts/utils/stack/runtime_state.mjs +2 -1
- package/scripts/utils/stack/startup.mjs +120 -13
- package/scripts/utils/stack/startup_server_light_dirs.test.mjs +64 -0
- package/scripts/utils/stack/startup_server_light_generate.test.mjs +70 -0
- package/scripts/utils/stack/startup_server_light_legacy.test.mjs +88 -0
- package/scripts/utils/stack/stop.mjs +15 -4
- package/scripts/utils/stack_context.mjs +23 -0
- package/scripts/utils/stack_runtime_state.mjs +104 -0
- package/scripts/utils/stacks.mjs +38 -0
- package/scripts/utils/tailscale/ip.mjs +116 -0
- package/scripts/utils/ui/ansi.mjs +39 -0
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/validate.mjs +88 -0
- package/scripts/where.mjs +2 -2
- package/scripts/worktrees.mjs +755 -179
- package/scripts/worktrees_archive_cmd.test.mjs +245 -0
- package/scripts/worktrees_cursor_monorepo_root.test.mjs +63 -0
- package/scripts/worktrees_list_specs_no_recurse.test.mjs +33 -0
- package/scripts/worktrees_monorepo_use_group.test.mjs +67 -0
- package/scripts/utils/dev/expo_web.mjs +0 -112
package/scripts/mobile.mjs
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import './utils/env/env.mjs';
|
|
2
2
|
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
-
import { pickNextFreeTcpPort } from './utils/net/ports.mjs';
|
|
4
3
|
import { run, runCapture, spawnProc } from './utils/proc/proc.mjs';
|
|
5
4
|
import { getComponentDir, getDefaultAutostartPaths, getRootDir } from './utils/paths/paths.mjs';
|
|
6
5
|
import { ensureDepsInstalled, pmExecBin, pmSpawnBin, requireDir } from './utils/proc/pm.mjs';
|
|
7
6
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
8
|
-
import { ensureExpoIsolationEnv, getExpoStatePaths
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
7
|
+
import { ensureExpoIsolationEnv, getExpoStatePaths } from './utils/expo/expo.mjs';
|
|
8
|
+
import { resolveServerPortFromEnv, resolveServerUrls } from './utils/server/urls.mjs';
|
|
9
|
+
import { resolveMobileExpoConfig } from './utils/mobile/config.mjs';
|
|
10
|
+
import { resolveStackContext } from './utils/stack/context.mjs';
|
|
11
|
+
import { expoExec } from './utils/expo/command.mjs';
|
|
12
|
+
import { ensureDevExpoServer } from './utils/dev/expo_dev.mjs';
|
|
13
|
+
import { resolveMobileReachableServerUrl } from './utils/server/mobile_api_url.mjs';
|
|
14
|
+
import { patchIosXcodeProjectsForSigningAndIdentity, resolveIosAppXcodeProjects } from './utils/mobile/ios_xcodeproj_patch.mjs';
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Mobile dev helper for the embedded `components/happy` Expo app.
|
|
@@ -70,19 +74,6 @@ async function main() {
|
|
|
70
74
|
await requireDir('happy', uiDir);
|
|
71
75
|
await ensureDepsInstalled(uiDir, 'happy');
|
|
72
76
|
|
|
73
|
-
const sanitizeBundleIdSegment = (s) =>
|
|
74
|
-
(s ?? '')
|
|
75
|
-
.toString()
|
|
76
|
-
.trim()
|
|
77
|
-
.toLowerCase()
|
|
78
|
-
.replace(/[^a-z0-9-]+/g, '-')
|
|
79
|
-
.replace(/^-+|-+$/g, '') || 'user';
|
|
80
|
-
|
|
81
|
-
const defaultLocalBundleId = (() => {
|
|
82
|
-
const user = sanitizeBundleIdSegment(process.env.USER ?? process.env.USERNAME ?? 'user');
|
|
83
|
-
return `com.happy.local.${user}.dev`;
|
|
84
|
-
})();
|
|
85
|
-
|
|
86
77
|
async function readXcdeviceList() {
|
|
87
78
|
if (process.platform !== 'darwin') {
|
|
88
79
|
return [];
|
|
@@ -97,21 +88,6 @@ async function main() {
|
|
|
97
88
|
// Default to the existing dev bundle identifier, which is also registered as a URL scheme
|
|
98
89
|
// (Info.plist includes `com.slopus.happy.dev`), so iOS will open the dev build instead of the App Store app.
|
|
99
90
|
const appEnv = process.env.APP_ENV ?? kv.get('--app-env') ?? 'development';
|
|
100
|
-
const iosAppName =
|
|
101
|
-
kv.get('--ios-app-name') ??
|
|
102
|
-
process.env.HAPPY_STACKS_IOS_APP_NAME ??
|
|
103
|
-
process.env.HAPPY_LOCAL_IOS_APP_NAME ??
|
|
104
|
-
'';
|
|
105
|
-
const iosBundleId =
|
|
106
|
-
kv.get('--ios-bundle-id') ??
|
|
107
|
-
process.env.HAPPY_STACKS_IOS_BUNDLE_ID ??
|
|
108
|
-
process.env.HAPPY_LOCAL_IOS_BUNDLE_ID ??
|
|
109
|
-
defaultLocalBundleId;
|
|
110
|
-
const scheme =
|
|
111
|
-
kv.get('--scheme') ??
|
|
112
|
-
process.env.HAPPY_STACKS_MOBILE_SCHEME ??
|
|
113
|
-
process.env.HAPPY_LOCAL_MOBILE_SCHEME ??
|
|
114
|
-
iosBundleId;
|
|
115
91
|
const host = kv.get('--host') ?? process.env.HAPPY_STACKS_MOBILE_HOST ?? process.env.HAPPY_LOCAL_MOBILE_HOST ?? 'lan';
|
|
116
92
|
const portRaw = kv.get('--port') ?? process.env.HAPPY_STACKS_MOBILE_PORT ?? process.env.HAPPY_LOCAL_MOBILE_PORT ?? '8081';
|
|
117
93
|
// Default behavior:
|
|
@@ -126,26 +102,56 @@ async function main() {
|
|
|
126
102
|
APP_ENV: appEnv,
|
|
127
103
|
};
|
|
128
104
|
|
|
105
|
+
const cfgBase = resolveMobileExpoConfig({ env });
|
|
106
|
+
const iosAppName = (kv.get('--ios-app-name') ?? cfgBase.iosAppName ?? '').toString();
|
|
107
|
+
const iosBundleId = (kv.get('--ios-bundle-id') ?? cfgBase.iosBundleId ?? '').toString();
|
|
108
|
+
const scheme = (kv.get('--scheme') ?? cfgBase.scheme ?? iosBundleId).toString();
|
|
109
|
+
|
|
129
110
|
const autostart = getDefaultAutostartPaths();
|
|
130
|
-
const
|
|
111
|
+
const stackCtx = resolveStackContext({ env, autostart });
|
|
112
|
+
const { stackMode, runtimeStatePath, stackName, envPath } = stackCtx;
|
|
113
|
+
|
|
114
|
+
// Ensure the built iOS app registers the same scheme we use for dev-client QR links.
|
|
115
|
+
// (Happy app reads EXPO_APP_SCHEME in app.config.js; default remains unchanged when unset.)
|
|
116
|
+
env.EXPO_APP_SCHEME = scheme;
|
|
117
|
+
// Ensure the app display name + bundle id are consistent with what we install.
|
|
118
|
+
// (app.config.js keeps upstream defaults unless these are explicitly set.)
|
|
119
|
+
if (iosAppName && iosAppName.trim()) {
|
|
120
|
+
env.EXPO_APP_NAME = iosAppName.trim();
|
|
121
|
+
}
|
|
122
|
+
if (iosBundleId && iosBundleId.trim()) {
|
|
123
|
+
env.EXPO_APP_BUNDLE_ID = iosBundleId.trim();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Always isolate Expo home + TMPDIR to avoid cross-worktree cache pollution (and to keep sandbox runs contained).
|
|
127
|
+
const expoPaths = getExpoStatePaths({
|
|
131
128
|
baseDir: autostart.baseDir,
|
|
132
|
-
kind: '
|
|
129
|
+
kind: 'expo-dev',
|
|
133
130
|
projectDir: uiDir,
|
|
134
|
-
stateFileName: '
|
|
131
|
+
stateFileName: 'expo.state.json',
|
|
135
132
|
});
|
|
136
133
|
await ensureExpoIsolationEnv({
|
|
137
134
|
env,
|
|
138
|
-
stateDir:
|
|
139
|
-
expoHomeDir:
|
|
140
|
-
tmpDir:
|
|
135
|
+
stateDir: expoPaths.stateDir,
|
|
136
|
+
expoHomeDir: expoPaths.expoHomeDir,
|
|
137
|
+
tmpDir: expoPaths.tmpDir,
|
|
141
138
|
});
|
|
142
139
|
|
|
143
140
|
// Allow happy-stacks to define the default server URL baked into the app bundle.
|
|
144
141
|
// This is read by the app via `process.env.EXPO_PUBLIC_HAPPY_SERVER_URL`.
|
|
145
|
-
const serverPort = resolveServerPortFromEnv({ env
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
142
|
+
const serverPort = resolveServerPortFromEnv({ env, defaultPort: 3005 });
|
|
143
|
+
const allowEnableTailscale =
|
|
144
|
+
!stackMode || stackName === 'main' || (env.HAPPY_STACKS_TAILSCALE_SERVE ?? env.HAPPY_LOCAL_TAILSCALE_SERVE ?? '0').toString().trim() === '1';
|
|
145
|
+
const resolvedUrls = await resolveServerUrls({ env, serverPort, allowEnable: allowEnableTailscale });
|
|
146
|
+
if (resolvedUrls.publicServerUrl && !env.EXPO_PUBLIC_HAPPY_SERVER_URL) {
|
|
147
|
+
env.EXPO_PUBLIC_HAPPY_SERVER_URL = resolvedUrls.publicServerUrl;
|
|
148
|
+
}
|
|
149
|
+
if (env.EXPO_PUBLIC_HAPPY_SERVER_URL) {
|
|
150
|
+
env.EXPO_PUBLIC_HAPPY_SERVER_URL = resolveMobileReachableServerUrl({
|
|
151
|
+
env,
|
|
152
|
+
serverUrl: env.EXPO_PUBLIC_HAPPY_SERVER_URL,
|
|
153
|
+
serverPort,
|
|
154
|
+
});
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
if (json) {
|
|
@@ -179,13 +185,12 @@ async function main() {
|
|
|
179
185
|
if (shouldClean) {
|
|
180
186
|
prebuildArgs.push('--clean');
|
|
181
187
|
}
|
|
182
|
-
await
|
|
188
|
+
await expoExec({ dir: uiDir, args: prebuildArgs, env, ensureDepsLabel: 'happy' });
|
|
183
189
|
|
|
184
190
|
// Always patch iOS props if iOS was generated.
|
|
185
191
|
if (platform === 'ios' || platform === 'all') {
|
|
186
192
|
const fs = await import('node:fs/promises');
|
|
187
193
|
const podPropsPath = `${uiDir}/ios/Podfile.properties.json`;
|
|
188
|
-
const pbxprojPath = `${uiDir}/ios/Happydev.xcodeproj/project.pbxproj`;
|
|
189
194
|
try {
|
|
190
195
|
const raw = await fs.readFile(podPropsPath, 'utf-8');
|
|
191
196
|
const json = JSON.parse(raw);
|
|
@@ -196,14 +201,17 @@ async function main() {
|
|
|
196
201
|
// ignore if path missing (platform != ios)
|
|
197
202
|
}
|
|
198
203
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
const iosProjects = await resolveIosAppXcodeProjects({ uiDir });
|
|
205
|
+
for (const project of iosProjects) {
|
|
206
|
+
try {
|
|
207
|
+
const raw = await fs.readFile(project.pbxprojPath, 'utf-8');
|
|
208
|
+
const next = raw.replaceAll('IPHONEOS_DEPLOYMENT_TARGET = 15.1;', 'IPHONEOS_DEPLOYMENT_TARGET = 16.0;');
|
|
209
|
+
if (next !== raw) {
|
|
210
|
+
await fs.writeFile(project.pbxprojPath, next, 'utf-8');
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
// ignore missing/invalid pbxproj; Expo will surface actionable errors if needed
|
|
204
214
|
}
|
|
205
|
-
} catch {
|
|
206
|
-
// ignore missing pbxproj (unexpected)
|
|
207
215
|
}
|
|
208
216
|
|
|
209
217
|
// Ensure CocoaPods doesn't crash due to locale issues.
|
|
@@ -264,25 +272,9 @@ async function main() {
|
|
|
264
272
|
// xcodebuild fails with:
|
|
265
273
|
// "Automatic signing is disabled ... pass -allowProvisioningUpdates"
|
|
266
274
|
//
|
|
267
|
-
// We force Expo CLI to go through its signing configuration path by clearing
|
|
268
|
-
// so it will re-set the team and include the provisioning flags.
|
|
269
|
-
|
|
270
|
-
const fs = await import('node:fs/promises');
|
|
271
|
-
const pbxprojPath = `${uiDir}/ios/Happydev.xcodeproj/project.pbxproj`;
|
|
272
|
-
const raw = await fs.readFile(pbxprojPath, 'utf-8');
|
|
273
|
-
let next = raw.replaceAll(/^\s*DEVELOPMENT_TEAM = ".*";\s*$/gm, '');
|
|
274
|
-
next = next.replaceAll(/PRODUCT_BUNDLE_IDENTIFIER = [^;]+;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${iosBundleId};`);
|
|
275
|
-
if (iosAppName && iosAppName.trim()) {
|
|
276
|
-
const name = iosAppName.trim();
|
|
277
|
-
const quoted = name.includes(' ') || name.includes('"') ? `"${name.replaceAll('"', '\\"')}"` : name;
|
|
278
|
-
next = next.replaceAll(/PRODUCT_NAME = [^;]+;/g, `PRODUCT_NAME = ${quoted};`);
|
|
279
|
-
}
|
|
280
|
-
if (next !== raw) {
|
|
281
|
-
await fs.writeFile(pbxprojPath, next, 'utf-8');
|
|
282
|
-
}
|
|
283
|
-
} catch {
|
|
284
|
-
// ignore
|
|
285
|
-
}
|
|
275
|
+
// We force Expo CLI to go through its signing configuration path by clearing any pre-existing
|
|
276
|
+
// team/profile identifiers, so it will re-set the team and include the provisioning flags.
|
|
277
|
+
await patchIosXcodeProjectsForSigningAndIdentity({ uiDir, iosBundleId, iosAppName });
|
|
286
278
|
}
|
|
287
279
|
|
|
288
280
|
const configuration = kv.get('--configuration') ?? 'Debug';
|
|
@@ -293,47 +285,39 @@ async function main() {
|
|
|
293
285
|
// Ensure CocoaPods doesn't crash due to locale issues.
|
|
294
286
|
env.LANG = env.LANG ?? 'en_US.UTF-8';
|
|
295
287
|
env.LC_ALL = env.LC_ALL ?? 'en_US.UTF-8';
|
|
296
|
-
await
|
|
288
|
+
await expoExec({ dir: uiDir, args, env, ensureDepsLabel: 'happy' });
|
|
297
289
|
}
|
|
298
290
|
|
|
299
291
|
if (!shouldStartMetro) {
|
|
300
292
|
return;
|
|
301
293
|
}
|
|
302
294
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const res = await killProcessGroupOwnedByStack(prevPid, { stackName, envPath, label: 'expo-mobile', json: true });
|
|
314
|
-
if (!res.killed) {
|
|
315
|
-
// eslint-disable-next-line no-console
|
|
316
|
-
console.warn(
|
|
317
|
-
`[mobile] not stopping existing Metro pid=${prevPid} because it does not look stack-owned.\n` +
|
|
318
|
-
`[mobile] continuing by starting a new Metro on a free port.`
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const requestedPort = Number.parseInt(String(portRaw), 10);
|
|
324
|
-
const startPort = Number.isFinite(requestedPort) && requestedPort > 0 ? requestedPort : 8081;
|
|
325
|
-
const portNumber = await pickNextFreeTcpPort(startPort);
|
|
326
|
-
env.RCT_METRO_PORT = String(portNumber);
|
|
295
|
+
// Unify Expo: one Expo dev server per stack/worktree. If dev mode already started Expo, we reuse it.
|
|
296
|
+
// If Expo is already running without dev-client enabled, we fail closed (no second Expo).
|
|
297
|
+
env.HAPPY_STACKS_EXPO_HOST = host;
|
|
298
|
+
env.HAPPY_LOCAL_EXPO_HOST = host;
|
|
299
|
+
env.HAPPY_STACKS_MOBILE_HOST = host;
|
|
300
|
+
env.HAPPY_LOCAL_MOBILE_HOST = host;
|
|
301
|
+
env.HAPPY_STACKS_MOBILE_SCHEME = scheme;
|
|
302
|
+
env.HAPPY_LOCAL_MOBILE_SCHEME = scheme;
|
|
303
|
+
env.HAPPY_STACKS_EXPO_DEV_PORT = String(portRaw);
|
|
304
|
+
env.HAPPY_LOCAL_EXPO_DEV_PORT = String(portRaw);
|
|
327
305
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
306
|
+
const children = [];
|
|
307
|
+
await ensureDevExpoServer({
|
|
308
|
+
startUi: false,
|
|
309
|
+
startMobile: true,
|
|
310
|
+
uiDir,
|
|
311
|
+
autostart,
|
|
312
|
+
baseEnv: env,
|
|
313
|
+
apiServerUrl: env.EXPO_PUBLIC_HAPPY_SERVER_URL ?? '',
|
|
314
|
+
restart,
|
|
315
|
+
stackMode,
|
|
316
|
+
runtimeStatePath,
|
|
317
|
+
stackName,
|
|
318
|
+
envPath,
|
|
319
|
+
children,
|
|
320
|
+
});
|
|
337
321
|
|
|
338
322
|
await new Promise(() => {});
|
|
339
323
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import './utils/env/env.mjs';
|
|
2
|
+
import { parseArgs } from './utils/cli/args.mjs';
|
|
3
|
+
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
4
|
+
import { run } from './utils/proc/proc.mjs';
|
|
5
|
+
import { getRootDir } from './utils/paths/paths.mjs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { defaultDevClientIdentity } from './utils/mobile/identifiers.mjs';
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
const { flags, kv } = parseArgs(argv);
|
|
13
|
+
const json = wantsJson(argv, { flags });
|
|
14
|
+
|
|
15
|
+
if (wantsHelp(argv, { flags }) || flags.has('--help') || argv.length === 0) {
|
|
16
|
+
printResult({
|
|
17
|
+
json,
|
|
18
|
+
data: {
|
|
19
|
+
flags: ['--device=<id-or-name>', '--clean', '--configuration=Debug|Release', '--json'],
|
|
20
|
+
},
|
|
21
|
+
text: [
|
|
22
|
+
'[mobile-dev-client] usage:',
|
|
23
|
+
' happys mobile-dev-client --install [--device=...] [--clean] [--configuration=Debug|Release] [--json]',
|
|
24
|
+
'',
|
|
25
|
+
'Notes:',
|
|
26
|
+
'- Installs a dedicated "Happy Stacks Dev" Expo dev-client app on your iPhone.',
|
|
27
|
+
'- This app is intended to be reused across stacks (no per-stack installs for dev-client).',
|
|
28
|
+
].join('\n'),
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!flags.has('--install')) {
|
|
34
|
+
printResult({
|
|
35
|
+
json,
|
|
36
|
+
data: { ok: false, error: 'missing_install_flag' },
|
|
37
|
+
text: '[mobile-dev-client] missing --install. Run: happys mobile-dev-client --help',
|
|
38
|
+
});
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const rootDir = getRootDir(import.meta.url);
|
|
43
|
+
const mobileScript = join(rootDir, 'scripts', 'mobile.mjs');
|
|
44
|
+
|
|
45
|
+
const device = kv.get('--device') ?? '';
|
|
46
|
+
const clean = flags.has('--clean');
|
|
47
|
+
const configuration = kv.get('--configuration') ?? 'Debug';
|
|
48
|
+
|
|
49
|
+
const id = defaultDevClientIdentity({ user: process.env.USER ?? process.env.USERNAME ?? 'user' });
|
|
50
|
+
|
|
51
|
+
const args = [
|
|
52
|
+
mobileScript,
|
|
53
|
+
'--app-env=development',
|
|
54
|
+
`--ios-app-name=${id.iosAppName}`,
|
|
55
|
+
`--ios-bundle-id=${id.iosBundleId}`,
|
|
56
|
+
`--scheme=${id.scheme}`,
|
|
57
|
+
'--prebuild',
|
|
58
|
+
...(clean ? ['--clean'] : []),
|
|
59
|
+
'--run-ios',
|
|
60
|
+
`--configuration=${configuration}`,
|
|
61
|
+
'--no-metro',
|
|
62
|
+
...(device ? [`--device=${device}`] : []),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const env = {
|
|
66
|
+
...process.env,
|
|
67
|
+
// Ensure Expo app config uses the dev-client scheme.
|
|
68
|
+
EXPO_APP_SCHEME: id.scheme,
|
|
69
|
+
// Ensure per-stack storage isolation is available during dev-client usage.
|
|
70
|
+
EXPO_PUBLIC_HAPPY_STORAGE_SCOPE: process.env.EXPO_PUBLIC_HAPPY_STORAGE_SCOPE ?? '',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const out = await run(process.execPath, args, { cwd: rootDir, env });
|
|
74
|
+
if (json) {
|
|
75
|
+
printResult({ json, data: { ok: true, installed: true, identity: id, out } });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch((err) => {
|
|
80
|
+
console.error('[mobile-dev-client] failed:', err);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
83
|
+
|