Conversation
…-native-first report
…uild checks - additive getSharedConfiguration() on ModuleFederationPluginV1/V2: returns the constructor shared option verbatim (pre deep-import injection) - extractShared.ts: evaluate an app's bundler config in-process (loadProjectConfig + synthetic env), duck-type the plugin instance, and resolve its shared option into virtual manifest entries per app root - dryRun.ts: runDryRun reuses the generalized checkSharedDeps codes over virtual entries; MISSING_SHARED_PROVIDER (warning) from the both-declared package.json heuristic; UNBUILT_CAVEAT suffix on every finding message so the locked --format json shape survives - federation-doctor --dry-run wiring: no builds, no manifest fetches, no MISSING_REMOTE_MANIFEST; ConfigEvalError -> message + exit 2, never a stack; exit codes 0/1/2 identical across modes - dry-run-workspace fixtures with per-app mini node_modules pins + CJS stub configs; .gitignore negations so fixture node_modules get committed - federation-doctor.mdx: --dry-run option docs Focused: jest -- extractShared dryRun federationDoctor doctor => 83/83. Full: 49 suites / 520 tests; pnpm typecheck, pnpm lint:ci clean.
…oling-side refusal
- --standalone boolean flag on start and bundle commands
- EnvOptions.argv (new additive public type): getEnvOptions always sets
argv = { standalone: <flag> }, so configs read env.argv?.standalone
safely; reaches defineRspackConfig/defineWebpackConfig evaluation
through the same { ...env, platform } spread as --platform — no second
injection point, nothing ever persisted
- assertStandaloneSupported in configFile.ts: refuses only when
repack-federation.json exists AND declares the matching remote entry
without standalone: true (standalone: false = unsupported); no file or
unmatched root proceeds; malformed file refuses as CLIError, no stack
- start/bundle call the guard up front when --standalone is requested —
refusal is tooling-side only, the bundler runtime never reads the
workspace map
- docs: --standalone sections on start/bundle CLI pages
Focused: jest -- getEnvOptions configFile options standalone => 53/53.
Full: 50 suites / 536 tests; pnpm typecheck, pnpm lint:ci clean.
…dvisories
- scanFeatures.ts: regex/state-machine scanner (no babel, zero new deps)
returning { dependencies, advisories }; a code-state walk masks comments
and string/template content (escape-tracked), collects static
import-from / bare import / export-from / require('lit') specifiers,
maps subpaths to package roots, ignores react/jsx-runtime noise, filters
relative and @/-alias imports, skips __tests__/ and *.test.* files and
unsupported extensions
- every dynamic pattern (import(), computed/template require) yields an
honesty advisory naming file:line and stating the set is NOT exhaustive
— manifest dynamicImportDetected semantics, never a silent pass
- features-store fixtures cover static imports, scoped packages, requires,
dynamic template-literal requires, commented/fake imports in strings and
comments, relative imports and test-file skips
- biome/tsconfig exclusions for src/commands/federation/__fixtures__:
fixture sources are input data with fake imports and line-significant
formatting — never linted, formatted or typechecked
(features-store/__tests__ uses .jsx so jest testMatch never collects it)
Focused: jest -- scanFeatures => 11/11.
Full: 51 suites / 547 tests; pnpm typecheck, pnpm lint:ci clean.
…e-write) with prompts
computeInitPlan computes everything federation-init would write without
writing it: key-level merges over package.json / repack-federation.json /
host remotes surgery (anchored, manual-steps degradation), create-only
versionless rspack+webpack configs consuming defineShared with the remote
role and env.argv-derived mode, host-vs-remote divergence and --yes
pin-rewrite plans. applyPlan writes exactly plan.files; prompt helpers
(apply [y/N], divergence [a]lign/[i]gnore/[c]ancel) take an injected ask.
Evidence:
- Focused: pnpm --filter @callstack/repack test -- initPlan initApply diff
-> 40 passed (3 suites); full suite 54 suites / 587 tests green.
- Gates: pnpm typecheck and pnpm lint:ci clean on this tree.
- Runtime harness: N/A at unit level — pure plan/merge/diff engine; the
real end-to-end run lands with the flat command into a scratch copy of
tester-federation (task 10.1 harness).
- Rollback: revert this commit — init/{plan,merge,templates,diff,apply,
prompt}.ts, the three suites and the init-workspace fixtures are new
files; nothing imports them yet (scanner untouched, no command wired).
react-native federation-init <feature-folder> --name <remote> composes
scan -> computeInitPlan -> formatPlanDiff -> prompt gate -> apply on the
workspace resolved from repack-federation.json. Command-level refusals
are exit 2 with clear messages and zero writes: missing --name/feature
folder, no/malformed workspace config, --standalone on a targeted remote
that does not declare it (assertStandaloneSupported), and manual steps
blocking --yes. Interactive runs show diffs first, then [a/i/c] on
divergence and apply [y/N]; --yes auto-aligns and reports each
pkg: old -> new pin rewrite.
Evidence:
- Focused: pnpm --filter @callstack/repack test -- index options
federationInit -> 37 passed (5 suites); full suite 55 suites /
602 tests green. pnpm typecheck and pnpm lint:ci clean.
- Runtime harness: ran the built dist command outside jest
(node -e require('dist/commands/federationInit.js')) against a scratch
copy of the init-workspace workspace: printed the full plan diff,
applied 6 files, and reported divergence + alignment
('remote-drift: react: ^9.8.7 -> 9.9.9' + package-manager-install
instruction). tester-federation end-to-end lands with 11.2: today's
tester app has no repack-federation.json (11.1 creates it), keeps
configs under configs/ (outside init's default discovery), and init
has no --config passthrough by design.
- Rollback: revert this commit — removes the command entry, options,
args type and doc page; the init/ engine files become inert (no
other importer).
Replace hardcoded 19.2.3/0.84.1 pins, pkg.dependencies lookups and
react(-native)/package.json proto-defineShared imports in all 8
tester-federation and tester-federation-v2 rspack/webpack configs with
Repack.defineShared(SHARED_DEPS, { context, role, mode }): host configs
role 'host' (eager), mini configs role 'remote' with the runtime-only
mode line via env.argv?.standalone. Exact pins now resolve from the
installed workspace — the old committed literals had drifted (react
19.2.3 vs installed 19.2.8, react-native 0.84.1 vs installed 0.86.0).
Add apps/tester-federation/repack-federation.json as the demo workspace
map (host root+manifest, MiniApp manifest/root/standalone/port) driving
zero-flag federation-doctor.
E2E smoke evidence (real rspack 1.6.0 + webpack 5.105.4 builds, ios):
- host/mini/v2-mini builds: compiled successfully, exit 0 (final configs)
- USE_WEBPACK=1 --bundler webpack host build: compiled successfully
- zero-flag federation-doctor from apps/tester-federation (config file
drives it): 0 errors, 7 EAGER_ADVISORY warnings, exit 0 — the
host-eager/remote-lazy pair reports as advisory, not EAGER_MISMATCH
- doctor --pairwise across both mini manifests: 0 errors, exit 0
- standalone flip: react-native webpack-bundle ... --standalone on mini
-> manifest shows all shared eager=true, git status shows no tracked
file changed (runtime-only via env.argv, nothing committed)
- manifests agree on every exact pin (doctor reports zero version drift)
…figs
Two defects surfaced by the 11.2 e2e smoke on real tester builds:
1. react-native federation-manifest/federation-init crashed under
@react-native-community/cli >= 17 (commander wiring): positionals not
declared in the command name never reach func — argv[0] carries the
parsed options object, so path.resolve threw ERR_INVALID_ARG_TYPE even
with --source given. Fix: declare optional positionals in the command
names ('federation-manifest [source]', 'federation-init
[feature-folder]') and trust only string argv[0], falling back to
parsed args otherwise. Reproduced live pre-fix, verified post-fix from
apps/tester-federation and a scratch init dogfood workspace.
2. federation-doctor --dry-run could not read the shared setup of remotes
scaffolded by federation-init, which generates rspack.<remote>.mts —
not a conventional rspack.config.* name. extractAppShared now falls
back to a tooling-style rspack.<name>.<ext> config when it is the only
rspack-prefixed candidate (webpack second, ambiguity never guessed).
Strict TDD: RED 1 failing discovery test + 3 new command/registry tests
failing for the wiring reason; GREEN 85/85 focused, full 55 suites/607
tests, typecheck, lint:ci. Live post-fix: zero-flag doctor exit 0 with
EAGER_ADVISORY only; --dry-run on an init-scaffolded scratch workspace
exit 0 (host via conventional name, remote via the new fallback); real
CLI 20 federation-init applied 5 files, idempotent re-run 'Nothing to do'
…ections - website: new docs/features/federation-workspace.md tying defineShared, repack-federation.json, federation-doctor (zero-flag/--dry-run/ --pairwise/EAGER_ADVISORY) and federation-init into one workflow guide, wired into the features _meta. - agent_context/federation-tools/design.md: PR 4 command-surface correction — the init codemod ships as the flat `react-native federation-init` command (no repack bin, no subcommand tree, no shared.config.ts convention) — plus as-built notes for defineShared, the workspace-map schema, doctor extensions, init behavior, and the RN CLI >= 17 positional-declaration contract. API doc completeness verified (not re-edited): define-shared.md, repack-federation-json.mdx, federation-init.mdx and federation-doctor.mdx already cover --dry-run/--pairwise/advisory semantics and --standalone (bundle/start pages).
The wizard's clack prompts rendered with no hint text, so nothing explained how to move, choose or exit. Every step now embeds its key legend in the visible message block (clack 0.9.1 has no prompt-level hint that renders eagerly), the dev banner closes with a global one-line legend (dim in color mode, plain bytes elsewhere), and the readline fallback carries equivalent inline legends. Legends are cosmetic: answers, exit codes and cancel semantics are untouched.
This reverts commit 644ca70.
|
@gedu is attempting to deploy a commit to the Callstack Team on Vercel. A member of the Team first needs to authorize it. |
🦋 Changeset detectedLatest commit: f1e39af The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The __fixtures__/rnbin tree sat under node_modules/ paths, so it was gitignored and invisible on CI: the fixture root missed with ENOENT and require.resolve walked up to the repo's real react-native, breaking 4 tests. Stand-in installs are now built under os.tmpdir() in beforeAll (symlink leg skips where symlinks are unavailable), and the dead local tree plus its config-split-roots pointer are gone.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What it does
One command starts a whole module-federation dev session, host plus any mini-app, each on its own port:
Everything it needs is the
repack-federation.jsonworkspace map from #1466, which this PR extends with two additive optional fields:configper app andporton the host.The live session is a terminal runner: an append-only log pane with
[app]prefixes (nothing above ever rewrites — clean scroll and copy), a fixed-height status block at the bottom (app/port/url/health, polled over each server's/status), a disclosed keymap (qquit,dopen host debugger, Ctrl-C for the ordered shutdown), and a plain no-chrome mode off-TTY so CI output stays grep-able.--jsonis a pure NDJSON contract (plan once, status on change) and never mixes chrome into stdout.How the runner works
Child-per-app: the supervisor spawns each selected app with its own
react-native start --bundler <detected per app>. A crashed child is a red row; siblings keep serving and the session ends nonzero only after everything is gone. Declared ports win;--auto-portsprobes busy ports before spawning.With
--launch(or the wizard asking, when a single platform is chosen), the runner also starts the app itself: once the host server goes running it spawns one supervisedrun-<platform> --no-packager [--device]from the app's own root — for standalone sessions, the standalone app's project — streaming gradle/Xcode output prefixed[launch]in the log pane. Device selection is delegated to the RN CLI.Test plan
pnpm start:drythenpnpm startinapps/tester-federation.apps/tester-federationwith one device/simulator connected,pnpm start, pick a platform, confirm the launch question → gradle/Xcode output streams under[launch]and the app comes up against the host dev server.Reviewer notes
@clack/prompts@^0.9.1inpackages/repack— the same librarypackages/initalready uses; wanted explicit sign-off for the core package. It is confined to the wizard behind a small injectable seam with a readline fallback, so dropping it later touches one module.--portoverrides the host port only; during long cold builds a row staysstartingwhile the child's prefixed logs stream real progress.