@happier-dev/stack 0.1.0-preview.17.1 → 0.1.0-preview.21.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/docs/server-flavors.md +6 -6
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts +2 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -1
- package/node_modules/@happier-dev/cli-common/dist/index.js +2 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -1
- package/node_modules/@happier-dev/cli-common/dist/providers/index.d.ts +51 -0
- package/node_modules/@happier-dev/cli-common/dist/providers/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/providers/index.js +129 -0
- package/node_modules/@happier-dev/cli-common/dist/providers/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.d.ts +5 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.js +5 -0
- package/node_modules/@happier-dev/cli-common/dist/service/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.d.ts +19 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.js +117 -0
- package/node_modules/@happier-dev/cli-common/dist/service/launchd.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.d.ts +55 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.js +302 -0
- package/node_modules/@happier-dev/cli-common/dist/service/manager.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.d.ts +12 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.js +75 -0
- package/node_modules/@happier-dev/cli-common/dist/service/systemd.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.d.ts +8 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.js +29 -0
- package/node_modules/@happier-dev/cli-common/dist/service/windows.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/package.json +11 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.d.ts +22 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.js +44 -0
- package/node_modules/@happier-dev/release-runtime/dist/assets.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.d.ts +5 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.js +21 -0
- package/node_modules/@happier-dev/release-runtime/dist/checksums.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.d.ts +14 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.js +39 -0
- package/node_modules/@happier-dev/release-runtime/dist/extractPlan.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.d.ts +20 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.js +60 -0
- package/node_modules/@happier-dev/release-runtime/dist/github.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.d.ts +7 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.js +7 -0
- package/node_modules/@happier-dev/release-runtime/dist/index.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.d.ts +7 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.js +92 -0
- package/node_modules/@happier-dev/release-runtime/dist/minisign.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.d.ts +26 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.d.ts.map +1 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.js +53 -0
- package/node_modules/@happier-dev/release-runtime/dist/verifiedDownload.js.map +1 -0
- package/node_modules/@happier-dev/release-runtime/package.json +38 -0
- package/package.json +4 -2
- package/scripts/auth.mjs +3 -2
- package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +1 -0
- package/scripts/auth_copy_from_runCapture.integration.test.mjs +8 -1
- package/scripts/auth_login_guided_server_no_expo.test.mjs +2 -0
- package/scripts/build.mjs +3 -18
- package/scripts/bundleWorkspaceDeps.mjs +5 -1
- package/scripts/bundleWorkspaceDeps.test.mjs +42 -1
- package/scripts/mobile.mjs +30 -2
- package/scripts/mobile_dev_client.mjs +7 -32
- package/scripts/mobile_dev_client_help_smoke.test.mjs +24 -0
- package/scripts/mobile_prebuild_happyDir_defined.test.mjs +47 -0
- package/scripts/mobile_prebuild_sets_rct_metro_port.test.mjs +81 -0
- package/scripts/mobile_run_ios_passes_port.integration.test.mjs +103 -0
- package/scripts/mobile_run_ios_uses_long_port_flag.test.mjs +106 -0
- package/scripts/providers_cmd.mjs +262 -0
- package/scripts/release_binary_smoke.integration.test.mjs +45 -37
- package/scripts/remote_cmd.mjs +352 -0
- package/scripts/self_host_daemon.real.integration.test.mjs +296 -0
- package/scripts/self_host_launchd.real.integration.test.mjs +211 -0
- package/scripts/self_host_runtime.mjs +1829 -327
- package/scripts/self_host_runtime.test.mjs +523 -1
- package/scripts/self_host_schtasks.real.integration.test.mjs +217 -0
- package/scripts/self_host_service_e2e_harness.mjs +93 -0
- package/scripts/self_host_systemd.real.integration.test.mjs +8 -86
- package/scripts/service.mjs +156 -26
- package/scripts/stack/command_arguments.mjs +1 -0
- package/scripts/stack/help_text.mjs +3 -1
- package/scripts/stack.mjs +2 -1
- package/scripts/stack_daemon_cmd.integration.test.mjs +37 -0
- package/scripts/stack_happy_cmd.integration.test.mjs +36 -0
- package/scripts/stack_pr_help_cmd.test.mjs +38 -0
- package/scripts/stop.mjs +2 -3
- package/scripts/utils/auth/credentials_paths.mjs +9 -9
- package/scripts/utils/auth/credentials_paths.test.mjs +8 -0
- package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +64 -3
- package/scripts/utils/auth/stable_scope_id.mjs +1 -1
- package/scripts/utils/cli/cli_registry.mjs +21 -0
- package/scripts/utils/cli/progress.mjs +8 -1
- package/scripts/utils/cli/progress.test.mjs +43 -0
- package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +17 -0
- package/scripts/utils/dev/expo_dev.mjs +35 -5
- package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +180 -1
- package/scripts/utils/dev/expo_dev_runtime_metadata.test.mjs +126 -0
- package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +9 -2
- package/scripts/utils/mobile/dev_client_install_invocation.mjs +68 -0
- package/scripts/utils/mobile/dev_client_install_invocation.test.mjs +27 -0
- package/scripts/utils/server/port.mjs +20 -2
- package/scripts/utils/service/service_manager.definition.test.mjs +66 -0
- package/scripts/utils/service/service_manager.mjs +96 -0
- package/scripts/utils/service/service_manager.plan.test.mjs +37 -0
- package/scripts/utils/service/service_manager.test.mjs +20 -0
- package/scripts/utils/service/systemd_service_unit.mjs +1 -0
- package/scripts/utils/service/systemd_service_unit.test.mjs +42 -0
- package/scripts/utils/service/windows_schtasks_wrapper.mjs +1 -0
- package/scripts/utils/service/windows_schtasks_wrapper.test.mjs +25 -0
- package/scripts/utils/ui/ui_export_env.mjs +29 -0
- package/scripts/utils/ui/ui_export_env.test.mjs +25 -0
- package/scripts/worktrees.mjs +3 -0
- package/scripts/worktrees_status_default_target.test.mjs +56 -0
package/scripts/mobile.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { expoExec } from './utils/expo/command.mjs';
|
|
|
14
14
|
import { ensureDevExpoServer } from './utils/dev/expo_dev.mjs';
|
|
15
15
|
import { resolveMobileReachableServerUrl } from './utils/server/mobile_api_url.mjs';
|
|
16
16
|
import { patchIosXcodeProjectsForSigningAndIdentity, resolveIosAppXcodeProjects } from './utils/mobile/ios_xcodeproj_patch.mjs';
|
|
17
|
+
import { pickMetroPort, resolveStablePortStart } from './utils/expo/metro_ports.mjs';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Mobile dev helper for the Happier UI Expo app (typically `apps/ui`).
|
|
@@ -75,6 +76,7 @@ async function main() {
|
|
|
75
76
|
const uiRepoDir = getComponentDir(rootDir, 'happier-ui');
|
|
76
77
|
await requireDir('happier-ui', uiRepoDir);
|
|
77
78
|
await ensureDepsInstalled(uiRepoDir, 'happier-ui');
|
|
79
|
+
const happyDir = uiRepoDir;
|
|
78
80
|
|
|
79
81
|
// Happy monorepo layouts (historical):
|
|
80
82
|
// - legacy: <happyDir>/expo-app (split-repo era)
|
|
@@ -104,7 +106,9 @@ async function main() {
|
|
|
104
106
|
// (Info.plist includes `dev.happier.app.dev`), so iOS will open the dev build instead of the App Store app.
|
|
105
107
|
const appEnv = process.env.APP_ENV ?? kv.get('--app-env') ?? 'development';
|
|
106
108
|
const host = kv.get('--host') ?? process.env.HAPPIER_STACK_MOBILE_HOST ?? 'lan';
|
|
107
|
-
const
|
|
109
|
+
const portFromArg = kv.get('--port') ?? '';
|
|
110
|
+
const portFromEnv = process.env.HAPPIER_STACK_MOBILE_PORT ?? '';
|
|
111
|
+
const portRaw = portFromArg || portFromEnv || '8081';
|
|
108
112
|
// Default behavior:
|
|
109
113
|
// - `hstack mobile` starts Metro and keeps running.
|
|
110
114
|
// - `hstack mobile --run-ios` / `hstack mobile:ios` just builds/installs and exits (unless --metro is provided).
|
|
@@ -126,6 +130,29 @@ async function main() {
|
|
|
126
130
|
const stackCtx = resolveStackContext({ env, autostart });
|
|
127
131
|
const { stackMode, runtimeStatePath, stackName, envPath } = stackCtx;
|
|
128
132
|
|
|
133
|
+
// Expo CLI resolves a Metro port early, but it won't actually bind it until late in the native build.
|
|
134
|
+
// If we stick to the default 8081, builds are much more likely to fail late if another Metro/Expo claims 8081 mid-build.
|
|
135
|
+
//
|
|
136
|
+
// Strategy:
|
|
137
|
+
// - If the user explicitly sets --port or HAPPIER_STACK_MOBILE_PORT, honor it.
|
|
138
|
+
// - Otherwise pick a stable, collision-resistant port in a higher range for build-only steps.
|
|
139
|
+
const needsNativeBuildPort = flags.has('--prebuild') || flags.has('--run-ios');
|
|
140
|
+
if (needsNativeBuildPort) {
|
|
141
|
+
const forcedPort = (portFromArg || portFromEnv).toString().trim();
|
|
142
|
+
const stableKey = (stackMode && stackName ? stackName : '') || iosBundleId || scheme || 'happier';
|
|
143
|
+
const startPort = resolveStablePortStart({
|
|
144
|
+
env,
|
|
145
|
+
stackName: stableKey,
|
|
146
|
+
baseKey: 'HAPPIER_STACK_MOBILE_BUILD_PORT_BASE',
|
|
147
|
+
rangeKey: 'HAPPIER_STACK_MOBILE_BUILD_PORT_RANGE',
|
|
148
|
+
defaultBase: 19000,
|
|
149
|
+
defaultRange: 10000,
|
|
150
|
+
});
|
|
151
|
+
const metroPort = await pickMetroPort({ startPort, forcedPort, host: '127.0.0.1' });
|
|
152
|
+
env.RCT_METRO_PORT = String(metroPort);
|
|
153
|
+
env.EXPO_PACKAGER_PORT = String(metroPort);
|
|
154
|
+
}
|
|
155
|
+
|
|
129
156
|
// Ensure the built iOS app registers the same scheme we use for dev-client QR links.
|
|
130
157
|
// (Happy app reads EXPO_APP_SCHEME in app.config.js; default remains unchanged when unset.)
|
|
131
158
|
env.EXPO_APP_SCHEME = scheme;
|
|
@@ -306,7 +333,8 @@ async function main() {
|
|
|
306
333
|
}
|
|
307
334
|
|
|
308
335
|
const configuration = kv.get('--configuration') ?? 'Debug';
|
|
309
|
-
const
|
|
336
|
+
const metroPort = String(env.RCT_METRO_PORT ?? portRaw ?? '8081');
|
|
337
|
+
const args = ['run:ios', '--port', metroPort, '--no-build-cache', '--configuration', configuration];
|
|
310
338
|
if (device) {
|
|
311
339
|
args.push('-d', device);
|
|
312
340
|
}
|
|
@@ -3,11 +3,10 @@ import { parseArgs } from './utils/cli/args.mjs';
|
|
|
3
3
|
import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
|
|
4
4
|
import { run } from './utils/proc/proc.mjs';
|
|
5
5
|
import { getRootDir } from './utils/paths/paths.mjs';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
6
|
import { banner, cmd, sectionTitle } from './utils/ui/layout.mjs';
|
|
8
7
|
import { cyan, dim, yellow } from './utils/ui/ansi.mjs';
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import { buildMobileDevClientInstallInvocation } from './utils/mobile/dev_client_install_invocation.mjs';
|
|
11
10
|
|
|
12
11
|
async function main() {
|
|
13
12
|
const argv = process.argv.slice(2);
|
|
@@ -18,13 +17,13 @@ async function main() {
|
|
|
18
17
|
printResult({
|
|
19
18
|
json,
|
|
20
19
|
data: {
|
|
21
|
-
flags: ['--device=<id-or-name>', '--clean', '--configuration=Debug|Release', '--json'],
|
|
20
|
+
flags: ['--device=<id-or-name>', '--port=<port>', '--clean', '--configuration=Debug|Release', '--json'],
|
|
22
21
|
},
|
|
23
22
|
text: [
|
|
24
23
|
banner('mobile-dev-client', { subtitle: 'Install the shared iOS dev-client app (one-time).' }),
|
|
25
24
|
'',
|
|
26
25
|
sectionTitle('usage:'),
|
|
27
|
-
` ${cyan('hstack mobile-dev-client')} --install [--device=...] [--clean] [--configuration=Debug|Release] [--json]`,
|
|
26
|
+
` ${cyan('hstack mobile-dev-client')} --install [--device=...] [--port=...] [--clean] [--configuration=Debug|Release] [--json]`,
|
|
28
27
|
'',
|
|
29
28
|
sectionTitle('notes:'),
|
|
30
29
|
`- Installs a dedicated ${cyan('hstack Dev')} Expo dev-client app on your iPhone.`,
|
|
@@ -45,39 +44,15 @@ async function main() {
|
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
const rootDir = getRootDir(import.meta.url);
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const device = kv.get('--device') ?? '';
|
|
51
|
-
const clean = flags.has('--clean');
|
|
52
|
-
const configuration = kv.get('--configuration') ?? 'Debug';
|
|
53
|
-
|
|
54
|
-
const id = defaultDevClientIdentity({ user: process.env.USER ?? process.env.USERNAME ?? 'user' });
|
|
55
|
-
|
|
56
|
-
const args = [
|
|
57
|
-
mobileScript,
|
|
58
|
-
'--app-env=development',
|
|
59
|
-
`--ios-app-name=${id.iosAppName}`,
|
|
60
|
-
`--ios-bundle-id=${id.iosBundleId}`,
|
|
61
|
-
`--scheme=${id.scheme}`,
|
|
62
|
-
'--prebuild',
|
|
63
|
-
...(clean ? ['--clean'] : []),
|
|
64
|
-
'--run-ios',
|
|
65
|
-
`--configuration=${configuration}`,
|
|
66
|
-
'--no-metro',
|
|
67
|
-
...(device ? [`--device=${device}`] : []),
|
|
68
|
-
];
|
|
47
|
+
const invocation = buildMobileDevClientInstallInvocation({ rootDir, argv, baseEnv: process.env });
|
|
69
48
|
|
|
70
49
|
const env = {
|
|
71
|
-
...
|
|
72
|
-
// Ensure Expo app config uses the dev-client scheme.
|
|
73
|
-
EXPO_APP_SCHEME: id.scheme,
|
|
74
|
-
// Ensure per-stack storage isolation is available during dev-client usage.
|
|
75
|
-
EXPO_PUBLIC_HAPPY_STORAGE_SCOPE: process.env.EXPO_PUBLIC_HAPPY_STORAGE_SCOPE ?? '',
|
|
50
|
+
...invocation.env,
|
|
76
51
|
};
|
|
77
52
|
|
|
78
|
-
const out = await run(process.execPath,
|
|
53
|
+
const out = await run(process.execPath, invocation.nodeArgs, { cwd: rootDir, env });
|
|
79
54
|
if (json) {
|
|
80
|
-
printResult({ json, data: { ok: true, installed: true, identity:
|
|
55
|
+
printResult({ json, data: { ok: true, installed: true, identity: invocation.identity, out } });
|
|
81
56
|
}
|
|
82
57
|
}
|
|
83
58
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
7
|
+
|
|
8
|
+
test('mobile-dev-client --help runs without syntax errors', async () => {
|
|
9
|
+
const scriptsDir = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const rootDir = dirname(scriptsDir);
|
|
11
|
+
const script = join(rootDir, 'scripts', 'mobile_dev_client.mjs');
|
|
12
|
+
|
|
13
|
+
const env = {
|
|
14
|
+
...process.env,
|
|
15
|
+
// Prevent env.mjs from selecting a real stack env file (keeps the test fast and hermetic).
|
|
16
|
+
HAPPIER_STACK_ENV_FILE: join(rootDir, 'scripts', 'nonexistent-env'),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const res = await runNodeCapture([script, '--help'], { cwd: rootDir, env });
|
|
20
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`);
|
|
21
|
+
assert.match(res.stdout, /\bmobile-dev-client\b/, `expected help to mention command name\nstdout:\n${res.stdout}`);
|
|
22
|
+
assert.match(res.stdout, /--port(?:=|\b)/, `expected help to mention --port\nstdout:\n${res.stdout}`);
|
|
23
|
+
});
|
|
24
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getStackRootFromMeta, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
8
|
+
|
|
9
|
+
test('hstack mobile --prebuild does not crash with undefined happyDir', async () => {
|
|
10
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
11
|
+
const mobileScript = join(rootDir, 'scripts', 'mobile.mjs');
|
|
12
|
+
|
|
13
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-mobile-'));
|
|
14
|
+
const repoDir = join(tmp, 'repo');
|
|
15
|
+
const storageDir = join(tmp, 'storage');
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Minimal fixture: the script expects a "happier-ui" dir to exist at <repo>/apps/ui.
|
|
19
|
+
// Avoid creating package.json to keep the run hermetic (ensureDepsInstalled will no-op).
|
|
20
|
+
await mkdir(join(repoDir, 'apps', 'ui'), { recursive: true });
|
|
21
|
+
|
|
22
|
+
// Ensure stack storage stays within this tmp dir (avoid touching real ~/.happier paths).
|
|
23
|
+
await mkdir(join(storageDir, 'main'), { recursive: true });
|
|
24
|
+
|
|
25
|
+
const env = {
|
|
26
|
+
...process.env,
|
|
27
|
+
HAPPIER_STACK_REPO_DIR: repoDir,
|
|
28
|
+
HAPPIER_STACK_HOME_DIR: join(tmp, 'home'),
|
|
29
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
30
|
+
HAPPIER_STACK_STACK: 'main',
|
|
31
|
+
// Keep resolveServerUrls from probing tailscale in tests.
|
|
32
|
+
HAPPIER_STACK_TAILSCALE_PREFER_PUBLIC_URL: '0',
|
|
33
|
+
HAPPIER_STACK_TAILSCALE_SERVE: '0',
|
|
34
|
+
// Prevent env auto-loading from a real machine stack env file.
|
|
35
|
+
HAPPIER_STACK_ENV_FILE: join(tmp, 'nonexistent-env'),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const res = await runNodeCapture([mobileScript, '--prebuild', '--no-metro'], { cwd: rootDir, env });
|
|
39
|
+
assert.doesNotMatch(
|
|
40
|
+
res.stderr,
|
|
41
|
+
/\bhappyDir is not defined\b/,
|
|
42
|
+
`expected prebuild to fail for a real reason (e.g. missing expo), not a ReferenceError\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`
|
|
43
|
+
);
|
|
44
|
+
} finally {
|
|
45
|
+
await rm(tmp, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getStackRootFromMeta, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
8
|
+
|
|
9
|
+
function parseKeyValueLines(text) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const line of String(text ?? '').split(/\r?\n/)) {
|
|
12
|
+
const idx = line.indexOf('=');
|
|
13
|
+
if (idx === -1) continue;
|
|
14
|
+
const key = line.slice(0, idx).trim();
|
|
15
|
+
const value = line.slice(idx + 1).trim();
|
|
16
|
+
if (key) out[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('hstack mobile --prebuild sets RCT_METRO_PORT for native build steps', async () => {
|
|
22
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
23
|
+
const mobileScript = join(rootDir, 'scripts', 'mobile.mjs');
|
|
24
|
+
|
|
25
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-mobile-port-'));
|
|
26
|
+
const repoDir = join(tmp, 'repo');
|
|
27
|
+
const storageDir = join(tmp, 'storage');
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const uiDir = join(repoDir, 'apps', 'ui');
|
|
31
|
+
const expoBin = join(uiDir, 'node_modules', '.bin', 'expo');
|
|
32
|
+
await mkdir(join(uiDir, 'node_modules', '.bin'), { recursive: true });
|
|
33
|
+
|
|
34
|
+
// Stub Expo CLI so the test doesn't require installing or running the real toolchain.
|
|
35
|
+
await writeFile(
|
|
36
|
+
expoBin,
|
|
37
|
+
`#!/usr/bin/env node
|
|
38
|
+
console.log('RCT_METRO_PORT=' + (process.env.RCT_METRO_PORT ?? ''));
|
|
39
|
+
console.log('EXPO_PACKAGER_PORT=' + (process.env.EXPO_PACKAGER_PORT ?? ''));
|
|
40
|
+
process.exit(0);
|
|
41
|
+
`,
|
|
42
|
+
'utf-8'
|
|
43
|
+
);
|
|
44
|
+
if (process.platform !== 'win32') {
|
|
45
|
+
await chmod(expoBin, 0o755);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure stack storage stays within this tmp dir (avoid touching real ~/.happier paths).
|
|
49
|
+
await mkdir(join(storageDir, 'main'), { recursive: true });
|
|
50
|
+
|
|
51
|
+
const env = {
|
|
52
|
+
...process.env,
|
|
53
|
+
HAPPIER_STACK_REPO_DIR: repoDir,
|
|
54
|
+
HAPPIER_STACK_HOME_DIR: join(tmp, 'home'),
|
|
55
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
56
|
+
HAPPIER_STACK_STACK: 'main',
|
|
57
|
+
// Keep resolveServerUrls from probing tailscale in tests.
|
|
58
|
+
HAPPIER_STACK_TAILSCALE_PREFER_PUBLIC_URL: '0',
|
|
59
|
+
HAPPIER_STACK_TAILSCALE_SERVE: '0',
|
|
60
|
+
// Prevent env auto-loading from a real machine stack env file.
|
|
61
|
+
HAPPIER_STACK_ENV_FILE: join(tmp, 'nonexistent-env'),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const res = await runNodeCapture([mobileScript, '--prebuild', '--platform=android', '--no-metro'], { cwd: rootDir, env });
|
|
65
|
+
const kv = parseKeyValueLines(res.stdout);
|
|
66
|
+
assert.ok(kv.RCT_METRO_PORT, `expected stub expo to print RCT_METRO_PORT\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
67
|
+
assert.equal(
|
|
68
|
+
kv.EXPO_PACKAGER_PORT,
|
|
69
|
+
kv.RCT_METRO_PORT,
|
|
70
|
+
`expected EXPO_PACKAGER_PORT to match RCT_METRO_PORT\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
71
|
+
);
|
|
72
|
+
assert.notEqual(
|
|
73
|
+
kv.RCT_METRO_PORT,
|
|
74
|
+
'8081',
|
|
75
|
+
`expected native build steps to avoid default Metro port 8081 (more collision-prone)\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
76
|
+
);
|
|
77
|
+
} finally {
|
|
78
|
+
await rm(tmp, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getStackRootFromMeta, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
8
|
+
|
|
9
|
+
function parseKeyValueLines(text) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const line of String(text ?? '').split(/\r?\n/)) {
|
|
12
|
+
const idx = line.indexOf('=');
|
|
13
|
+
if (idx === -1) continue;
|
|
14
|
+
const key = line.slice(0, idx).trim();
|
|
15
|
+
const value = line.slice(idx + 1).trim();
|
|
16
|
+
if (key) out[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('hstack mobile --run-ios passes --port to Expo so the native build and dev server use the same Metro port', async () => {
|
|
22
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
23
|
+
const mobileScript = join(rootDir, 'scripts', 'mobile.mjs');
|
|
24
|
+
|
|
25
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-mobile-runios-port-'));
|
|
26
|
+
const repoDir = join(tmp, 'repo');
|
|
27
|
+
const storageDir = join(tmp, 'storage');
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const binDir = join(tmp, 'bin');
|
|
31
|
+
await mkdir(binDir, { recursive: true });
|
|
32
|
+
const xcrunStub = join(binDir, 'xcrun');
|
|
33
|
+
await writeFile(
|
|
34
|
+
xcrunStub,
|
|
35
|
+
`#!/bin/bash
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
if [[ "\${1:-}" == "xcdevice" && "\${2:-}" == "list" ]]; then
|
|
38
|
+
echo "[]"
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
echo "xcrun stub: unsupported args: $*" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
`,
|
|
44
|
+
'utf-8'
|
|
45
|
+
);
|
|
46
|
+
if (process.platform !== 'win32') {
|
|
47
|
+
await chmod(xcrunStub, 0o755);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const uiDir = join(repoDir, 'apps', 'ui');
|
|
51
|
+
const expoBin = join(uiDir, 'node_modules', '.bin', 'expo');
|
|
52
|
+
await mkdir(join(uiDir, 'node_modules', '.bin'), { recursive: true });
|
|
53
|
+
|
|
54
|
+
// Stub Expo CLI: print argv + env-derived ports and exit successfully.
|
|
55
|
+
// Use an absolute node shebang so the test can safely clear PATH.
|
|
56
|
+
await writeFile(
|
|
57
|
+
expoBin,
|
|
58
|
+
`#!${process.execPath}
|
|
59
|
+
console.log('ARGS=' + JSON.stringify(process.argv.slice(2)));
|
|
60
|
+
console.log('RCT_METRO_PORT=' + (process.env.RCT_METRO_PORT ?? ''));
|
|
61
|
+
console.log('EXPO_PACKAGER_PORT=' + (process.env.EXPO_PACKAGER_PORT ?? ''));
|
|
62
|
+
process.exit(0);
|
|
63
|
+
`,
|
|
64
|
+
'utf-8'
|
|
65
|
+
);
|
|
66
|
+
if (process.platform !== 'win32') {
|
|
67
|
+
await chmod(expoBin, 0o755);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await mkdir(join(storageDir, 'main'), { recursive: true });
|
|
71
|
+
|
|
72
|
+
const env = {
|
|
73
|
+
...process.env,
|
|
74
|
+
// Ensure xcrun runs fast/deterministically in tests.
|
|
75
|
+
// Include system paths so env.mjs won't prepend /usr/bin ahead of our stub.
|
|
76
|
+
PATH: `${binDir}:/usr/bin:/bin`,
|
|
77
|
+
HAPPIER_STACK_REPO_DIR: repoDir,
|
|
78
|
+
HAPPIER_STACK_HOME_DIR: join(tmp, 'home'),
|
|
79
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
80
|
+
HAPPIER_STACK_STACK: 'main',
|
|
81
|
+
HAPPIER_STACK_TAILSCALE_PREFER_PUBLIC_URL: '0',
|
|
82
|
+
HAPPIER_STACK_TAILSCALE_SERVE: '0',
|
|
83
|
+
HAPPIER_STACK_ENV_FILE: join(tmp, 'nonexistent-env'),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const res = await runNodeCapture([mobileScript, '--run-ios', '--no-metro'], { cwd: rootDir, env });
|
|
87
|
+
const kv = parseKeyValueLines(res.stdout);
|
|
88
|
+
|
|
89
|
+
const args = JSON.parse(kv.ARGS ?? '[]');
|
|
90
|
+
const port = kv.RCT_METRO_PORT ?? '';
|
|
91
|
+
|
|
92
|
+
assert.ok(port, `expected RCT_METRO_PORT to be set\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
93
|
+
assert.equal(kv.EXPO_PACKAGER_PORT, port, `expected EXPO_PACKAGER_PORT to match\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
94
|
+
assert.ok(!args.includes('-p'), `expected expo args to not include -p\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
95
|
+
assert.ok(!args.includes('--no-bundler'), `expected expo args to not include --no-bundler\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
96
|
+
assert.ok(args.includes('--port'), `expected expo args to include --port\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
97
|
+
const portIdx = args.indexOf('--port');
|
|
98
|
+
assert.ok(portIdx >= 0 && args[portIdx + 1] === port, `expected --port to match RCT_METRO_PORT\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
99
|
+
assert.notEqual(port, '8081', `expected non-default port to reduce collisions\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
100
|
+
} finally {
|
|
101
|
+
await rm(tmp, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { chmod, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getStackRootFromMeta, runNodeCapture } from './testkit/auth_testkit.mjs';
|
|
8
|
+
|
|
9
|
+
function parseKeyValueLines(text) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const line of String(text ?? '').split(/\r?\n/)) {
|
|
12
|
+
const idx = line.indexOf('=');
|
|
13
|
+
if (idx === -1) continue;
|
|
14
|
+
const key = line.slice(0, idx).trim();
|
|
15
|
+
const value = line.slice(idx + 1).trim();
|
|
16
|
+
if (key) out[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('hstack mobile --run-ios passes --port (long flag) so Expo uses the requested Metro port', async () => {
|
|
22
|
+
const rootDir = getStackRootFromMeta(import.meta.url);
|
|
23
|
+
const mobileScript = join(rootDir, 'scripts', 'mobile.mjs');
|
|
24
|
+
|
|
25
|
+
const tmp = await mkdtemp(join(tmpdir(), 'hstack-mobile-runios-longport-'));
|
|
26
|
+
const repoDir = join(tmp, 'repo');
|
|
27
|
+
const storageDir = join(tmp, 'storage');
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const binDir = join(tmp, 'bin');
|
|
31
|
+
await mkdir(binDir, { recursive: true });
|
|
32
|
+
const xcrunStub = join(binDir, 'xcrun');
|
|
33
|
+
await writeFile(
|
|
34
|
+
xcrunStub,
|
|
35
|
+
`#!/bin/bash
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
if [[ "\${1:-}" == "xcdevice" && "\${2:-}" == "list" ]]; then
|
|
38
|
+
echo "[]"
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
echo "xcrun stub: unsupported args: $*" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
`,
|
|
44
|
+
'utf-8'
|
|
45
|
+
);
|
|
46
|
+
if (process.platform !== 'win32') {
|
|
47
|
+
await chmod(xcrunStub, 0o755);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const uiDir = join(repoDir, 'apps', 'ui');
|
|
51
|
+
const expoBin = join(uiDir, 'node_modules', '.bin', 'expo');
|
|
52
|
+
await mkdir(join(uiDir, 'node_modules', '.bin'), { recursive: true });
|
|
53
|
+
|
|
54
|
+
await writeFile(
|
|
55
|
+
expoBin,
|
|
56
|
+
`#!${process.execPath}
|
|
57
|
+
console.log('ARGS=' + JSON.stringify(process.argv.slice(2)));
|
|
58
|
+
console.log('RCT_METRO_PORT=' + (process.env.RCT_METRO_PORT ?? ''));
|
|
59
|
+
console.log('EXPO_PACKAGER_PORT=' + (process.env.EXPO_PACKAGER_PORT ?? ''));
|
|
60
|
+
process.exit(0);
|
|
61
|
+
`,
|
|
62
|
+
'utf-8'
|
|
63
|
+
);
|
|
64
|
+
if (process.platform !== 'win32') {
|
|
65
|
+
await chmod(expoBin, 0o755);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await mkdir(join(storageDir, 'main'), { recursive: true });
|
|
69
|
+
|
|
70
|
+
const env = {
|
|
71
|
+
...process.env,
|
|
72
|
+
// Ensure our xcrun stub wins over /usr/bin/xcrun even after env.mjs normalizes PATH.
|
|
73
|
+
PATH: `${binDir}:/usr/bin:/bin`,
|
|
74
|
+
HAPPIER_STACK_REPO_DIR: repoDir,
|
|
75
|
+
HAPPIER_STACK_HOME_DIR: join(tmp, 'home'),
|
|
76
|
+
HAPPIER_STACK_STORAGE_DIR: storageDir,
|
|
77
|
+
HAPPIER_STACK_STACK: 'main',
|
|
78
|
+
HAPPIER_STACK_TAILSCALE_PREFER_PUBLIC_URL: '0',
|
|
79
|
+
HAPPIER_STACK_TAILSCALE_SERVE: '0',
|
|
80
|
+
HAPPIER_STACK_ENV_FILE: join(tmp, 'nonexistent-env'),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const res = await runNodeCapture([mobileScript, '--run-ios', '--no-metro', '--port=14362'], { cwd: rootDir, env });
|
|
84
|
+
assert.equal(res.code, 0, `expected exit 0, got ${res.code}\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`);
|
|
85
|
+
const kv = parseKeyValueLines(res.stdout);
|
|
86
|
+
const args = JSON.parse(kv.ARGS ?? '[]');
|
|
87
|
+
|
|
88
|
+
assert.ok(!args.includes('--no-bundler'), `expected expo args to not include --no-bundler\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
89
|
+
assert.ok(args.includes('--port'), `expected expo args to include --port\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
90
|
+
assert.ok(!args.includes('-p'), `expected expo args to not include -p\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
91
|
+
const portIdx = args.indexOf('--port');
|
|
92
|
+
assert.ok(portIdx >= 0 && args[portIdx + 1] === '14362', `expected --port 14362\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`);
|
|
93
|
+
assert.equal(
|
|
94
|
+
kv.RCT_METRO_PORT,
|
|
95
|
+
'14362',
|
|
96
|
+
`expected RCT_METRO_PORT to be set from hstack --port\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
97
|
+
);
|
|
98
|
+
assert.equal(
|
|
99
|
+
kv.EXPO_PACKAGER_PORT,
|
|
100
|
+
'14362',
|
|
101
|
+
`expected EXPO_PACKAGER_PORT to match\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`
|
|
102
|
+
);
|
|
103
|
+
} finally {
|
|
104
|
+
await rm(tmp, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
});
|