mapples 0.2.0-beta.10 → 0.2.0-beta.13
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/dist/api/client.d.ts +15 -0
- package/dist/api/client.js +36 -0
- package/dist/api/client.js.map +1 -1
- package/dist/api/commit.js +19 -1
- package/dist/api/commit.js.map +1 -1
- package/dist/api/errors.d.ts +1 -1
- package/dist/api/errors.js +3 -0
- package/dist/api/errors.js.map +1 -1
- package/dist/api/objects.d.ts +3 -1
- package/dist/api/objects.js +31 -4
- package/dist/api/objects.js.map +1 -1
- package/dist/api/project.d.ts +11 -0
- package/dist/api/project.js +29 -0
- package/dist/api/project.js.map +1 -0
- package/dist/assets/assets.d.ts +12 -0
- package/dist/assets/assets.js +31 -2
- package/dist/assets/assets.js.map +1 -1
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/impl/createImpl.js +26 -4
- package/dist/commands/impl/createImpl.js.map +1 -1
- package/dist/commands/impl/doctorImpl.js +14 -7
- package/dist/commands/impl/doctorImpl.js.map +1 -1
- package/dist/commands/impl/initImpl.js +34 -1
- package/dist/commands/impl/initImpl.js.map +1 -1
- package/dist/commands/init.js +1 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/nav/navigatorTree.d.ts +36 -2
- package/dist/nav/navigatorTree.js +191 -40
- package/dist/nav/navigatorTree.js.map +1 -1
- package/dist/nav/routeTree.d.ts +2 -1
- package/dist/nav/routeTree.js +9 -4
- package/dist/nav/routeTree.js.map +1 -1
- package/dist/nav/screenOptions.d.ts +24 -0
- package/dist/nav/screenOptions.js +41 -13
- package/dist/nav/screenOptions.js.map +1 -1
- package/dist/runtime/runtimeFiles.d.ts +17 -0
- package/dist/runtime/runtimeFiles.js +183 -0
- package/dist/runtime/runtimeFiles.js.map +1 -0
- package/dist/services/appDeps.d.ts +32 -0
- package/dist/services/appDeps.js +132 -0
- package/dist/services/appDeps.js.map +1 -0
- package/dist/services/doctorService.d.ts +19 -1
- package/dist/services/doctorService.js +117 -3
- package/dist/services/doctorService.js.map +1 -1
- package/dist/services/scaffold.d.ts +13 -1
- package/dist/services/scaffold.js +57 -5
- package/dist/services/scaffold.js.map +1 -1
- package/dist/services/syncEngine.js +85 -22
- package/dist/services/syncEngine.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +21 -0
- package/dist/version.js +40 -3
- package/dist/version.js.map +1 -1
- package/package.json +4 -3
- package/templates/agent/mapples.md +9 -4
- package/templates/agent/skills/mapples-conventions/SKILL.md +2 -2
- package/templates/agent/skills/mapples-design/SKILL.md +4 -4
- package/templates/agent/skills/mapples-design/reference/component-library.md +1 -1
- package/templates/agent/skills/mapples-design/reference/layout-contract.md +2 -3
- package/templates/agent/skills/mapples-design/reference/playbooks/onboarding-flow.md +1 -1
- package/templates/agent/skills/mapples-design/reference/playbooks/screen-flow-wiring.md +2 -5
- package/templates/app/_layout.tsx +6 -1
- package/templates/gitignore +2 -0
- package/templates/metro.config.js +11 -0
- package/templates/package.json +19 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctorImpl.js","sourceRoot":"","sources":["../../../src/commands/impl/doctorImpl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"doctorImpl.js","sourceRoot":"","sources":["../../../src/commands/impl/doctorImpl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE5F,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAA6B;IAC3D,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACvE,IAAI,SAAS;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAE5D,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,IAAI,KAAK,EAAE,MAAM,EAAE,EACtE,IAAI,EACJ,CAAC,CACF,CACF,CAAC;QACF,IAAI,OAAO;YAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC5C,MAAM,KAAK,GACT,GAAG,YAAY,qBAAqB,MAAM,CAAC,KAAK,aAAa,MAAM,CAAC,UAAU,kBAAkB,MAAM,CAAC,OAAO,YAAY;QAC1H,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,sBAAsB,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7E,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CACT,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CACzF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO;QAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -7,7 +7,8 @@ import { DEFAULT_PAGES_DIR, DEFAULT_ROUTES_DIR, writeConfig, configPath, } from
|
|
|
7
7
|
import { assetUrlFor, resolveApiUrl } from '../../project/env.js';
|
|
8
8
|
import { writeSecret } from '../../project/secret.js';
|
|
9
9
|
import { agentNextSteps, installAgentIntegration, hooksSnippetHeading, mcpSnippetHeading, parseAgentName, } from '../../services/agentInstall.js';
|
|
10
|
-
import {
|
|
10
|
+
import { addRuntimeDependencies, assertLinkableApp, detectPackageManager, runInstall, } from '../../services/appDeps.js';
|
|
11
|
+
import { ensureBabelPlugin, ensureMetroConfig, writeSidTypes } from '../../services/scaffold.js';
|
|
11
12
|
import { runSyncEngine } from '../../services/syncEngine.js';
|
|
12
13
|
import { renderReport } from '../../ui/report.js';
|
|
13
14
|
import { cliOrigin } from '../../version.js';
|
|
@@ -21,10 +22,22 @@ export async function runInit(opts) {
|
|
|
21
22
|
if (existsSync(configPath(root))) {
|
|
22
23
|
throw new Error('this app is already linked (.mapples/config.json exists) — run `mapples sync`');
|
|
23
24
|
}
|
|
25
|
+
// Refuse an app Mapples cannot run in BEFORE anything is written.
|
|
26
|
+
assertLinkableApp(root);
|
|
24
27
|
const agent = parseAgentName(opts.agent);
|
|
25
28
|
const secret = await askSecret(opts);
|
|
26
29
|
const apiUrl = resolveApiUrl({ apiUrl: opts.apiUrl, denv: opts.denv });
|
|
27
30
|
const { projectId, name } = await resolveProject(apiUrl, secret);
|
|
31
|
+
// The synced files import @mapples/* (and metro/babel configs below need
|
|
32
|
+
// @mapples/app/expo + babel-plugin-mapples-sid): add what the app lacks and
|
|
33
|
+
// install it with the app's own package manager before linking.
|
|
34
|
+
const pm = detectPackageManager(root);
|
|
35
|
+
const deps = addRuntimeDependencies(root);
|
|
36
|
+
const install = opts.install !== false;
|
|
37
|
+
if (deps.added.length > 0 && install) {
|
|
38
|
+
console.log(chalk.gray(`\nadding ${deps.added.length} dependencies, running \`${pm} install\`…`));
|
|
39
|
+
runInstall(root, pm);
|
|
40
|
+
}
|
|
28
41
|
writeConfig(root, {
|
|
29
42
|
projectId,
|
|
30
43
|
apiUrl,
|
|
@@ -36,6 +49,12 @@ export async function runInit(opts) {
|
|
|
36
49
|
console.log(chalk.bold('\nmapples init') + (name ? chalk.gray(` — project “${name}”`) : ''));
|
|
37
50
|
console.log(` ${chalk.green('A')} .mapples/config.json`);
|
|
38
51
|
console.log(` ${chalk.green('A')} .mapples/secret ${chalk.gray('(gitignored)')}`);
|
|
52
|
+
if (deps.added.length > 0) {
|
|
53
|
+
console.log(` ${chalk.yellow('M')} package.json ${chalk.gray(`— added ${deps.added.map((d) => `${d.name}@${d.spec}`).join(', ')}`)}`);
|
|
54
|
+
}
|
|
55
|
+
for (const m of deps.mismatched) {
|
|
56
|
+
console.log(chalk.yellow(` ⚠ ${m.name} is "${m.spec}" — every @mapples/* package should use "${m.expected}" (one release group; not changed automatically)`));
|
|
57
|
+
}
|
|
39
58
|
if (writeSidTypes(root))
|
|
40
59
|
console.log(` ${chalk.green('A')} mapples/sid.d.ts`);
|
|
41
60
|
const babel = ensureBabelPlugin(root);
|
|
@@ -44,6 +63,12 @@ export async function runInit(opts) {
|
|
|
44
63
|
if (babel === 'manual') {
|
|
45
64
|
console.log(chalk.yellow(` ⚠ add 'babel-plugin-mapples-sid' to the plugins of your babel.config.js (not edited automatically)`));
|
|
46
65
|
}
|
|
66
|
+
const metro = ensureMetroConfig(root);
|
|
67
|
+
if (metro === 'created')
|
|
68
|
+
console.log(` ${chalk.green('A')} metro.config.js`);
|
|
69
|
+
if (metro === 'manual') {
|
|
70
|
+
console.log(chalk.yellow(` ⚠ wrap your metro.config.js resolveRequest with mapplesResolver from '@mapples/app/expo' (not edited automatically)`));
|
|
71
|
+
}
|
|
47
72
|
console.log(chalk.gray(` ℹ ensure mapples/sid.d.ts is included by your tsconfig (add it to "include" if needed)`));
|
|
48
73
|
let mcpSnippet = null;
|
|
49
74
|
let hooksSnippet = null;
|
|
@@ -74,6 +99,14 @@ export async function runInit(opts) {
|
|
|
74
99
|
throw err;
|
|
75
100
|
console.log(chalk.yellow(' offline — run `mapples sync` once you are online'));
|
|
76
101
|
}
|
|
102
|
+
console.log([
|
|
103
|
+
'',
|
|
104
|
+
chalk.bold('Next steps:'),
|
|
105
|
+
...(deps.added.length > 0 && !install ? [` ${pm} install`] : []),
|
|
106
|
+
` npx expo start — Mapples screens live under ${DEFAULT_ROUTES_DIR}/`,
|
|
107
|
+
' npx expo run:ios|android for a native build (Metro on :8081; --port <n> if another Metro holds it)',
|
|
108
|
+
' mapples sync — after edits in Creator or in code',
|
|
109
|
+
].join('\n'));
|
|
77
110
|
if (agent) {
|
|
78
111
|
if (mcpSnippet)
|
|
79
112
|
console.log(['', mcpSnippetHeading(agent), mcpSnippet].join('\n'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initImpl.js","sourceRoot":"","sources":["../../../src/commands/impl/initImpl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,UAAU,GACX,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,GACf,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"initImpl.js","sourceRoot":"","sources":["../../../src/commands/impl/initImpl.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -7,6 +7,7 @@ export function registerInit(program) {
|
|
|
7
7
|
.option('--api-url <url>', 'Mapples API base URL')
|
|
8
8
|
.option('--agent <name>', 'also set up an AI agent: claude (Claude Code) | cursor | codex (ChatGPT) — skills, instructions, MCP entry')
|
|
9
9
|
.addOption(new Option('--denv <env>').hideHelp()) // internal: dev|staging|prod
|
|
10
|
+
.option('--no-install', 'add the Mapples dependencies to package.json without installing them')
|
|
10
11
|
.option('--json', 'machine-readable output')
|
|
11
12
|
.action(async (opts) => {
|
|
12
13
|
const { runInit } = await import('./impl/initImpl.js');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,MAAM,WAAW,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,gDAAgD,CAAC;SAC7D,MAAM,CAAC,mBAAmB,EAAE,kDAAkD,CAAC;SAC/E,MAAM,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;SACjD,MAAM,CACL,gBAAgB,EAChB,4GAA4G,CAC7G;SACA,SAAS,CAAC,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,6BAA6B;SAC9E,MAAM,CAAC,QAAQ,EAAE,yBAAyB,CAAC;SAC3C,MAAM,CAAC,KAAK,EAAE,IAA6B,EAAE,EAAE;QAC9C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACvD,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,MAAM,WAAW,CAAC;AAE5C,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,gDAAgD,CAAC;SAC7D,MAAM,CAAC,mBAAmB,EAAE,kDAAkD,CAAC;SAC/E,MAAM,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;SACjD,MAAM,CACL,gBAAgB,EAChB,4GAA4G,CAC7G;SACA,SAAS,CAAC,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,6BAA6B;SAC9E,MAAM,CAAC,cAAc,EAAE,sEAAsE,CAAC;SAC9F,MAAM,CAAC,QAAQ,EAAE,yBAAyB,CAAC;SAC3C,MAAM,CAAC,KAAK,EAAE,IAA6B,EAAE,EAAE;QAC9C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACvD,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -36,12 +36,33 @@ export interface NavigatorDoc {
|
|
|
36
36
|
export declare function extractNavigatorDoc(raw: unknown): NavigatorDoc | null;
|
|
37
37
|
export declare function slugSegment(name: string): string;
|
|
38
38
|
export declare function humanizeSegment(segment: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* A designed tab bar / drawer: the Creator component the navigator names
|
|
41
|
+
* (`options.tabBar.componentUuid` / `options.drawer.componentUuid`) and the
|
|
42
|
+
* file `mapples sync` writes it to, relative to the project root.
|
|
43
|
+
*/
|
|
44
|
+
export interface ChromeComponentFile {
|
|
45
|
+
name: string;
|
|
46
|
+
file: string;
|
|
47
|
+
}
|
|
48
|
+
/** How layouts reach files outside the route tree. */
|
|
49
|
+
export interface LayoutContext {
|
|
50
|
+
/** The project's routes directory (`app`, `app/(mapples)`), relative to the root. */
|
|
51
|
+
routesDir: string;
|
|
52
|
+
/** Creator component uuid → its file; resolves designed navigator chrome. */
|
|
53
|
+
chromeFiles: Map<string, ChromeComponentFile>;
|
|
54
|
+
}
|
|
39
55
|
interface PlannedLayout {
|
|
40
56
|
file: string;
|
|
41
57
|
content: string;
|
|
42
58
|
}
|
|
43
59
|
export interface NavPlan extends RoutePlan {
|
|
44
60
|
layouts: PlannedLayout[];
|
|
61
|
+
/**
|
|
62
|
+
* Route id → the expo-router screen names from the root navigator down to
|
|
63
|
+
* it (`['(main)', 'profile']`) — what the app runtime navigates by.
|
|
64
|
+
*/
|
|
65
|
+
screensByRoute: Map<string, string[]>;
|
|
45
66
|
/** Navigator id by directory (relative to routesDir, '' = root) — adoption places files by it. */
|
|
46
67
|
navigatorByDir: Map<string, string>;
|
|
47
68
|
/** Navigators no route reaches from the root, by the `(group)` dir they were generated into. */
|
|
@@ -53,7 +74,20 @@ export interface NavPlan extends RoutePlan {
|
|
|
53
74
|
* segment) lets the layout pass after local renames honour the files as the
|
|
54
75
|
* user left them.
|
|
55
76
|
*/
|
|
56
|
-
|
|
77
|
+
/**
|
|
78
|
+
* A route path as expo-router file segments: a Creator `:id` param is the
|
|
79
|
+
* `[id]` dynamic segment (`product/:id` → `product/[id]`).
|
|
80
|
+
*/
|
|
81
|
+
export declare function pathToSegment(path: string): string;
|
|
82
|
+
export declare function buildNavPlan(doc: NavigatorDoc, pages: Map<string, PageEntity>, notes: string[], segmentOverrides?: Map<string, string>, context?: LayoutContext): NavPlan;
|
|
83
|
+
/** Creator component uuid → its synced file, for designed navigator chrome. */
|
|
84
|
+
export declare function chromeFilesFor(components: Map<string, {
|
|
85
|
+
uuid?: string;
|
|
86
|
+
name: string;
|
|
87
|
+
}>, componentsMap: Record<string, {
|
|
88
|
+
file: string;
|
|
89
|
+
name: string;
|
|
90
|
+
}>): Map<string, ChromeComponentFile>;
|
|
57
91
|
/** Every layout the CLI writes opens with this; it is how a CLI-owned file is told from a user's. */
|
|
58
92
|
export declare const GENERATED_LAYOUT_HEADER: RegExp;
|
|
59
93
|
/**
|
|
@@ -72,7 +106,7 @@ export interface NavigatorSyncResult {
|
|
|
72
106
|
* push local file renames back as route renames. Runs BEFORE the page merge
|
|
73
107
|
* so the merge reads and writes each page at its final location.
|
|
74
108
|
*/
|
|
75
|
-
export declare function syncNavigatorTree(root: string, routesDir: string, pagesDir: string, pages: Map<string, PageEntity>, pagesMap: PagesMap, baseRaw: NavigationDocument | null, remoteRaw: NavigationDocument | null, report: SyncReport): NavigatorSyncResult;
|
|
109
|
+
export declare function syncNavigatorTree(root: string, routesDir: string, pagesDir: string, pages: Map<string, PageEntity>, pagesMap: PagesMap, baseRaw: NavigationDocument | null, remoteRaw: NavigationDocument | null, report: SyncReport, chromeFiles: Map<string, ChromeComponentFile>): NavigatorSyncResult;
|
|
76
110
|
/** Put the merged doc back into whatever wrapper shape the server used. */
|
|
77
111
|
export declare function rewrapDoc(raw: NavigationDocument | null, doc: NavigatorDoc): NavigationDocument;
|
|
78
112
|
export {};
|
|
@@ -30,13 +30,14 @@
|
|
|
30
30
|
// may target them). Its pages never use `index`: each answers at its own
|
|
31
31
|
// slug, so pages.json records a real path for them.
|
|
32
32
|
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
33
|
-
import { dirname, join, relative } from 'node:path';
|
|
33
|
+
import { basename, dirname, join, relative } from 'node:path';
|
|
34
34
|
import { printValue } from '@mapples/sid-ast';
|
|
35
35
|
import { withRouteMarker } from '../codegen/emit.js';
|
|
36
|
+
import { writeNavigationModule } from '../runtime/runtimeFiles.js';
|
|
36
37
|
import { deprecateFile } from '../merge/deprecate.js';
|
|
37
38
|
import { deepEqual } from '../merge/mergePatch.js';
|
|
38
39
|
import { makePlan, placePages, pruneEmptyDirs, publicPath, } from './placement.js';
|
|
39
|
-
import { hiddenScreenOptions, mapNavigatorOptions, mapScreenOptions } from './screenOptions.js';
|
|
40
|
+
import { defaultScreenOptions, hiddenScreenOptions, isRef, mapNavigatorOptions, mapScreenOptions, } from './screenOptions.js';
|
|
40
41
|
/** Accepts the canonical doc, or the backend's {id: 'nav_default', data: doc} wrapper. */
|
|
41
42
|
export function extractNavigatorDoc(raw) {
|
|
42
43
|
if (typeof raw !== 'object' || raw === null)
|
|
@@ -88,19 +89,38 @@ function uniqueSegment(segment, used) {
|
|
|
88
89
|
* segment) lets the layout pass after local renames honour the files as the
|
|
89
90
|
* user left them.
|
|
90
91
|
*/
|
|
91
|
-
|
|
92
|
+
/**
|
|
93
|
+
* A route path as expo-router file segments: a Creator `:id` param is the
|
|
94
|
+
* `[id]` dynamic segment (`product/:id` → `product/[id]`).
|
|
95
|
+
*/
|
|
96
|
+
export function pathToSegment(path) {
|
|
97
|
+
return (path
|
|
98
|
+
.replace(/^\/+/, '')
|
|
99
|
+
.replace(/\/+$/, '')
|
|
100
|
+
.replace(/(^|\/):([A-Za-z_][A-Za-z0-9_]*)(?=\/|$)/g, '$1[$2]') || 'index');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The expo-router screen names above the root navigator: the route groups
|
|
104
|
+
* `routesDir` adds below `app/` (`app/(mapples)` → `['(mapples)']`).
|
|
105
|
+
*/
|
|
106
|
+
function rootScreenPrefix(routesDir) {
|
|
107
|
+
const parts = routesDir.split(/[\\/]+/).filter(Boolean);
|
|
108
|
+
return parts[0] === 'app' ? parts.slice(1) : parts;
|
|
109
|
+
}
|
|
110
|
+
export function buildNavPlan(doc, pages, notes, segmentOverrides = new Map(), context = { routesDir: 'app', chromeFiles: new Map() }) {
|
|
92
111
|
const pageByKey = pageKeyIndex(pages);
|
|
93
112
|
const routes = [];
|
|
94
113
|
const layouts = [];
|
|
95
114
|
const navigatorByDir = new Map();
|
|
96
115
|
const detachedByDir = new Map();
|
|
116
|
+
const screensByRoute = new Map();
|
|
97
117
|
const seen = new Set();
|
|
98
118
|
const segmentFor = (route, isInitial) => {
|
|
99
119
|
const override = segmentOverrides.get(route.id);
|
|
100
120
|
if (override)
|
|
101
121
|
return override;
|
|
102
122
|
if (route.path)
|
|
103
|
-
return route.path
|
|
123
|
+
return pathToSegment(route.path);
|
|
104
124
|
if (route.target.kind === 'page')
|
|
105
125
|
return isInitial ? 'index' : slugSegment(route.name);
|
|
106
126
|
return isInitial ? `(${slugSegment(route.name)})` : slugSegment(route.name);
|
|
@@ -110,7 +130,9 @@ export function buildNavPlan(doc, pages, notes, segmentOverrides = new Map()) {
|
|
|
110
130
|
* caller emits this one (the root's screen list grows with detached groups
|
|
111
131
|
* first). `detached` subtrees never use `index`/`(group)` positions.
|
|
112
132
|
*/
|
|
113
|
-
const walk = (navigatorId, dir, detached, used
|
|
133
|
+
const walk = (navigatorId, dir, detached, used,
|
|
134
|
+
/** Screen names from the root down to this navigator. */
|
|
135
|
+
chain) => {
|
|
114
136
|
if (seen.has(navigatorId)) {
|
|
115
137
|
notes.push(`navigation: navigator ${navigatorId} is referenced twice — second reference skipped`);
|
|
116
138
|
return null;
|
|
@@ -128,7 +150,9 @@ export function buildNavPlan(doc, pages, notes, segmentOverrides = new Map()) {
|
|
|
128
150
|
const isInitial = !detached && route.id === initialId;
|
|
129
151
|
const segment = uniqueSegment(segmentFor(route, isInitial), used);
|
|
130
152
|
const where = `route “${route.name}” of navigator “${nav.name}”`;
|
|
131
|
-
const
|
|
153
|
+
const mapped = mapScreenOptions(route, nav.type, where, notes);
|
|
154
|
+
const options = { ...defaultScreenOptions(route, nav, mapped), ...mapped };
|
|
155
|
+
screensByRoute.set(route.id, [...chain, segment]);
|
|
132
156
|
if (route.target.kind === 'page') {
|
|
133
157
|
const pageId = pageByKey.get(route.target.pageUuid);
|
|
134
158
|
if (!pageId) {
|
|
@@ -145,22 +169,27 @@ export function buildNavPlan(doc, pages, notes, segmentOverrides = new Map()) {
|
|
|
145
169
|
routeName: route.name,
|
|
146
170
|
routePath: publicPath(dir, segment),
|
|
147
171
|
});
|
|
148
|
-
screens.push({ segment, options });
|
|
172
|
+
screens.push({ segment, options, routeId: route.id });
|
|
149
173
|
}
|
|
150
174
|
else {
|
|
151
|
-
screens.push({ segment, options });
|
|
175
|
+
screens.push({ segment, options, routeId: route.id });
|
|
152
176
|
const childDir = dir ? join(dir, segment) : segment;
|
|
153
|
-
const child = walk(route.target.navigatorId, childDir, detached, new Set()
|
|
177
|
+
const child = walk(route.target.navigatorId, childDir, detached, new Set(), [
|
|
178
|
+
...chain,
|
|
179
|
+
segment,
|
|
180
|
+
]);
|
|
154
181
|
if (child)
|
|
155
|
-
layouts.push(layoutFor(child.nav, childDir, child.screens, notes));
|
|
182
|
+
layouts.push(layoutFor(child.nav, childDir, child.screens, notes, context));
|
|
156
183
|
}
|
|
157
184
|
}
|
|
158
185
|
return { nav, screens };
|
|
159
186
|
};
|
|
160
187
|
const rootUsed = new Set();
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
188
|
+
const prefix = rootScreenPrefix(context.routesDir);
|
|
189
|
+
const root = walk(doc.rootNavigatorId, '', false, rootUsed, prefix);
|
|
190
|
+
if (!root) {
|
|
191
|
+
return { ...makePlan(routes), layouts, navigatorByDir, detachedByDir, screensByRoute };
|
|
192
|
+
}
|
|
164
193
|
// ---- detached navigators: hidden (group)s under the root directory ----
|
|
165
194
|
const referenced = new Set([doc.rootNavigatorId]);
|
|
166
195
|
for (const nav of Object.values(doc.navigators)) {
|
|
@@ -172,11 +201,11 @@ export function buildNavPlan(doc, pages, notes, segmentOverrides = new Map()) {
|
|
|
172
201
|
const attach = (navigatorId) => {
|
|
173
202
|
const nav = doc.navigators[navigatorId];
|
|
174
203
|
const segment = uniqueSegment(`(${slugSegment(nav.name)})`, rootUsed);
|
|
175
|
-
const group = walk(navigatorId, segment, true, new Set());
|
|
204
|
+
const group = walk(navigatorId, segment, true, new Set(), [...prefix, segment]);
|
|
176
205
|
if (!group)
|
|
177
206
|
return;
|
|
178
207
|
detachedByDir.set(segment, navigatorId);
|
|
179
|
-
layouts.push(layoutFor(group.nav, segment, group.screens, notes));
|
|
208
|
+
layouts.push(layoutFor(group.nav, segment, group.screens, notes, context));
|
|
180
209
|
root.screens.push({ segment, options: hiddenScreenOptions(root.nav.type) });
|
|
181
210
|
};
|
|
182
211
|
// Unreferenced navigators first (deterministic: document order), then whatever
|
|
@@ -189,15 +218,30 @@ export function buildNavPlan(doc, pages, notes, segmentOverrides = new Map()) {
|
|
|
189
218
|
if (!seen.has(id))
|
|
190
219
|
attach(id);
|
|
191
220
|
}
|
|
192
|
-
layouts.push(layoutFor(root.nav, '', root.screens, notes));
|
|
193
|
-
return { ...makePlan(routes), layouts, navigatorByDir, detachedByDir };
|
|
221
|
+
layouts.push(layoutFor(root.nav, '', root.screens, notes, context));
|
|
222
|
+
return { ...makePlan(routes), layouts, navigatorByDir, detachedByDir, screensByRoute };
|
|
194
223
|
}
|
|
195
|
-
function layoutFor(nav, dir, screens, notes) {
|
|
224
|
+
function layoutFor(nav, dir, screens, notes, context) {
|
|
225
|
+
const file = dir ? join(dir, '_layout.tsx') : '_layout.tsx';
|
|
196
226
|
return {
|
|
197
|
-
file
|
|
198
|
-
content: layoutContent(nav, screens, notes
|
|
227
|
+
file,
|
|
228
|
+
content: layoutContent(nav, screens, notes, dir === '', {
|
|
229
|
+
...context,
|
|
230
|
+
layoutFile: join(context.routesDir, file),
|
|
231
|
+
}),
|
|
199
232
|
};
|
|
200
233
|
}
|
|
234
|
+
/** Creator component uuid → its synced file, for designed navigator chrome. */
|
|
235
|
+
export function chromeFilesFor(components, componentsMap) {
|
|
236
|
+
const files = new Map();
|
|
237
|
+
for (const [id, component] of components) {
|
|
238
|
+
const mapping = componentsMap[id];
|
|
239
|
+
if (!mapping)
|
|
240
|
+
continue;
|
|
241
|
+
files.set(component.uuid ?? id, { name: mapping.name, file: mapping.file });
|
|
242
|
+
}
|
|
243
|
+
return files;
|
|
244
|
+
}
|
|
201
245
|
// ---- emission ----
|
|
202
246
|
const NAVIGATOR_IMPORT = {
|
|
203
247
|
stack: { name: 'Stack', module: 'expo-router' },
|
|
@@ -206,15 +250,25 @@ const NAVIGATOR_IMPORT = {
|
|
|
206
250
|
};
|
|
207
251
|
/** Every layout the CLI writes opens with this; it is how a CLI-owned file is told from a user's. */
|
|
208
252
|
export const GENERATED_LAYOUT_HEADER = /^\/\/\s*GENERATED by Mapples/;
|
|
209
|
-
/**
|
|
210
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Options literal; a `tabBarIcon`/`drawerIcon` string becomes an icon
|
|
255
|
+
* renderer: a Creator reference through `RouteIcon` (the Play runtime's
|
|
256
|
+
* resolver), a bare glyph name through `Icon`.
|
|
257
|
+
*/
|
|
258
|
+
function optionsLiteral(options, uses) {
|
|
211
259
|
const iconKeys = ['tabBarIcon', 'drawerIcon'];
|
|
212
260
|
const plain = {};
|
|
213
261
|
const iconParts = [];
|
|
214
262
|
for (const [key, value] of Object.entries(options)) {
|
|
215
263
|
if (iconKeys.includes(key) && typeof value === 'string') {
|
|
216
|
-
|
|
217
|
-
|
|
264
|
+
if (isRef(value)) {
|
|
265
|
+
uses.routeIcon = true;
|
|
266
|
+
iconParts.push(`${key}: ({ color, size }) => <RouteIcon icon=${JSON.stringify(value)} color={color} size={size} />`);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
uses.icon = true;
|
|
270
|
+
iconParts.push(`${key}: ({ color }) => <Icon iconName=${JSON.stringify(value)} color={color} />`);
|
|
271
|
+
}
|
|
218
272
|
}
|
|
219
273
|
else {
|
|
220
274
|
plain[key] = value;
|
|
@@ -226,32 +280,119 @@ function optionsLiteral(options, usesIcon) {
|
|
|
226
280
|
const inner = base === '{}' ? '' : base.slice(1, -1).trim().replace(/,\s*$/, '') + ', ';
|
|
227
281
|
return `{ ${inner}${iconParts.join(', ')} }`;
|
|
228
282
|
}
|
|
229
|
-
|
|
283
|
+
/** An import path from one project file to another, extension dropped. */
|
|
284
|
+
function moduleFrom(fromFile, toFile) {
|
|
285
|
+
let rel = relative(dirname(fromFile), toFile)
|
|
286
|
+
.split(/[\\/]+/)
|
|
287
|
+
.join('/')
|
|
288
|
+
.replace(/\.tsx?$/, '');
|
|
289
|
+
if (!rel.startsWith('.'))
|
|
290
|
+
rel = `./${rel}`;
|
|
291
|
+
return rel;
|
|
292
|
+
}
|
|
293
|
+
function chromeWiring(nav, notes, context, taken) {
|
|
294
|
+
const options = (nav.options ?? {});
|
|
295
|
+
if (nav.type === 'tabs' && options.tabBar?.native) {
|
|
296
|
+
notes.push(`navigation: navigator “${nav.name}” uses the native tab bar — generated layouts render the JavaScript tab bar until NativeTabs emission lands`);
|
|
297
|
+
}
|
|
298
|
+
const uuid = nav.type === 'tabs'
|
|
299
|
+
? options.tabBar?.componentUuid
|
|
300
|
+
: nav.type === 'drawer'
|
|
301
|
+
? options.drawer?.componentUuid
|
|
302
|
+
: undefined;
|
|
303
|
+
if (!uuid)
|
|
304
|
+
return null;
|
|
305
|
+
const chrome = context.chromeFiles.get(uuid);
|
|
306
|
+
if (!chrome) {
|
|
307
|
+
notes.push(`navigation: navigator “${nav.name}” is designed with component ${uuid}, which has no synced file — the layout renders the stock ${nav.type === 'tabs' ? 'tab bar' : 'drawer'}`);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
// The component file's own name (`MainTabBar.tsx` → `MainTabBar`), the
|
|
311
|
+
// identifier its default export already carries.
|
|
312
|
+
let component = basename(chrome.file)
|
|
313
|
+
.replace(/\.tsx?$/, '')
|
|
314
|
+
.replace(/[^A-Za-z0-9_$]/g, '') || 'NavigatorChrome';
|
|
315
|
+
if (/^[0-9]/.test(component))
|
|
316
|
+
component = `Chrome${component}`;
|
|
317
|
+
while (taken.has(component))
|
|
318
|
+
component = `${component}Chrome`;
|
|
319
|
+
return {
|
|
320
|
+
prop: nav.type === 'tabs' ? 'tabBar' : 'drawerContent',
|
|
321
|
+
host: nav.type === 'tabs' ? 'TabBarHost' : 'DrawerContentHost',
|
|
322
|
+
component,
|
|
323
|
+
module: moduleFrom(context.layoutFile, chrome.file),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function layoutContent(nav, screens, notes, isRoot, context) {
|
|
230
327
|
const kind = NAVIGATOR_IMPORT[nav.type] ?? NAVIGATOR_IMPORT.stack;
|
|
231
|
-
const
|
|
328
|
+
const uses = { icon: false, routeIcon: false };
|
|
232
329
|
const screenLines = screens.map(({ segment, options }) => {
|
|
233
330
|
if (Object.keys(options).length === 0) {
|
|
234
331
|
return ` <${kind.name}.Screen name=${JSON.stringify(segment)} />`;
|
|
235
332
|
}
|
|
236
|
-
return ` <${kind.name}.Screen name=${JSON.stringify(segment)} options={${optionsLiteral(options,
|
|
333
|
+
return ` <${kind.name}.Screen name=${JSON.stringify(segment)} options={${optionsLiteral(options, uses)}} />`;
|
|
237
334
|
});
|
|
238
335
|
const navOptions = mapNavigatorOptions(nav.options, nav.type, `navigator “${nav.name}”`, notes);
|
|
239
|
-
const screenOptions = Object.keys(navOptions).length > 0
|
|
240
|
-
? ` screenOptions={${optionsLiteral(navOptions, usesIcon)}}`
|
|
241
|
-
: '';
|
|
242
336
|
const componentName = `${nav.name.replace(/[^A-Za-z0-9]/g, '') || 'Mapples'}Layout`;
|
|
337
|
+
const chrome = chromeWiring(nav, notes, context, new Set([componentName, kind.name, 'MapplesApp', 'Icon', 'RouteIcon', 'StatusBar']));
|
|
338
|
+
// Each screen the Creator routes gets its route's behaviour (params,
|
|
339
|
+
// on-enter action, guard, page variables) from @mapples/app.
|
|
340
|
+
const routed = screens.filter((screen) => screen.routeId !== undefined);
|
|
341
|
+
const screenLayout = routed.length > 0
|
|
342
|
+
? [
|
|
343
|
+
'',
|
|
344
|
+
'// Creator route of each screen: params, on-enter action, guard, page variables.',
|
|
345
|
+
'const screenLayout = mapplesScreens({',
|
|
346
|
+
...routed.map((screen) => ` ${printValue({ [screen.segment]: screen.routeId }).slice(2, -2)},`),
|
|
347
|
+
'});',
|
|
348
|
+
]
|
|
349
|
+
: [];
|
|
350
|
+
const navigatorProps = [
|
|
351
|
+
...(routed.length > 0 ? [' screenLayout={screenLayout}'] : []),
|
|
352
|
+
...(chrome
|
|
353
|
+
? [` ${chrome.prop}={(props) => <${chrome.host} {...props} Chrome={${chrome.component}} />}`]
|
|
354
|
+
: []),
|
|
355
|
+
...(Object.keys(navOptions).length > 0
|
|
356
|
+
? [` screenOptions={${optionsLiteral(navOptions, uses)}}`]
|
|
357
|
+
: []),
|
|
358
|
+
].join('');
|
|
359
|
+
// The root layout mounts MapplesApp (tokens, fonts, assets and the
|
|
360
|
+
// behaviour runtime, from the files `mapples sync` writes); nested
|
|
361
|
+
// navigator layouts render inside it.
|
|
362
|
+
const indent = isRoot ? ' ' : '';
|
|
363
|
+
const navigator = [
|
|
364
|
+
` <${kind.name}${navigatorProps}>`,
|
|
365
|
+
...screenLines,
|
|
366
|
+
` </${kind.name}>`,
|
|
367
|
+
].map((line) => indent + line);
|
|
368
|
+
const appNames = [
|
|
369
|
+
...(routed.length > 0 ? ['mapplesScreens'] : []),
|
|
370
|
+
...(uses.routeIcon ? ['RouteIcon'] : []),
|
|
371
|
+
];
|
|
372
|
+
const appImport = isRoot
|
|
373
|
+
? appNames.length > 0
|
|
374
|
+
? `import MapplesApp, { ${appNames.join(', ')} } from '@mapples/app';`
|
|
375
|
+
: "import MapplesApp from '@mapples/app';"
|
|
376
|
+
: appNames.length > 0
|
|
377
|
+
? `import { ${appNames.join(', ')} } from '@mapples/app';`
|
|
378
|
+
: null;
|
|
243
379
|
return [
|
|
244
380
|
'// GENERATED by Mapples — DO NOT EDIT. This layout is owned by the CLI and',
|
|
245
381
|
`// regenerated from the navigation document (navigator “${nav.name}”, ${nav.type})`,
|
|
246
382
|
'// on every `mapples sync`. Edit navigation in Creator instead.',
|
|
383
|
+
...(appImport ? [appImport] : []),
|
|
384
|
+
...(chrome ? [`import { ${chrome.host} } from '@mapples/navigation';`] : []),
|
|
385
|
+
...(uses.icon ? ["import { Icon } from '@mapples/ui';"] : []),
|
|
247
386
|
`import { ${kind.name} } from '${kind.module}';`,
|
|
248
|
-
...(
|
|
387
|
+
...(isRoot ? ["import { StatusBar } from 'expo-status-bar';"] : []),
|
|
388
|
+
...(chrome ? [`import ${chrome.component} from '${chrome.module}';`] : []),
|
|
389
|
+
...screenLayout,
|
|
249
390
|
'',
|
|
250
391
|
`export default function ${componentName}() {`,
|
|
251
392
|
' return (',
|
|
252
|
-
|
|
253
|
-
...
|
|
254
|
-
|
|
393
|
+
...(isRoot ? [' <MapplesApp>'] : []),
|
|
394
|
+
...navigator,
|
|
395
|
+
...(isRoot ? [' <StatusBar style="auto" />', ' </MapplesApp>'] : []),
|
|
255
396
|
' );',
|
|
256
397
|
'}',
|
|
257
398
|
'',
|
|
@@ -266,7 +407,8 @@ function layoutContent(nav, screens, notes) {
|
|
|
266
407
|
export function writeOwnedLayout(root, routesDir, file, content, report) {
|
|
267
408
|
const rel = join(routesDir, file);
|
|
268
409
|
const abs = join(root, rel);
|
|
269
|
-
|
|
410
|
+
const existed = existsSync(abs);
|
|
411
|
+
if (existed) {
|
|
270
412
|
const current = readFileSync(abs, 'utf8');
|
|
271
413
|
if (current === content)
|
|
272
414
|
return;
|
|
@@ -281,7 +423,11 @@ export function writeOwnedLayout(root, routesDir, file, content, report) {
|
|
|
281
423
|
}
|
|
282
424
|
mkdirSync(dirname(abs), { recursive: true });
|
|
283
425
|
writeFileSync(abs, content);
|
|
284
|
-
report.files.push({
|
|
426
|
+
report.files.push({
|
|
427
|
+
file: rel,
|
|
428
|
+
action: existed ? 'updated' : 'created',
|
|
429
|
+
detail: 'CLI-owned layout',
|
|
430
|
+
});
|
|
285
431
|
}
|
|
286
432
|
/** Remove generated layouts the plan no longer has (a navigator moved or went away). Never a user file. */
|
|
287
433
|
function pruneStaleLayouts(root, routesDir, planned, report) {
|
|
@@ -319,12 +465,13 @@ function pruneStaleLayouts(root, routesDir, planned, report) {
|
|
|
319
465
|
* push local file renames back as route renames. Runs BEFORE the page merge
|
|
320
466
|
* so the merge reads and writes each page at its final location.
|
|
321
467
|
*/
|
|
322
|
-
export function syncNavigatorTree(root, routesDir, pagesDir, pages, pagesMap, baseRaw, remoteRaw, report) {
|
|
468
|
+
export function syncNavigatorTree(root, routesDir, pagesDir, pages, pagesMap, baseRaw, remoteRaw, report, chromeFiles) {
|
|
323
469
|
const remoteDoc = extractNavigatorDoc(remoteRaw);
|
|
324
470
|
const notes = [];
|
|
325
|
-
const
|
|
471
|
+
const context = { routesDir, chromeFiles };
|
|
472
|
+
const plan = remoteDoc ? buildNavPlan(remoteDoc, pages, notes, new Map(), context) : makePlan([]);
|
|
326
473
|
const baseDoc = extractNavigatorDoc(baseRaw);
|
|
327
|
-
const basePlan = baseDoc ? buildNavPlan(baseDoc, pages, []) : null;
|
|
474
|
+
const basePlan = baseDoc ? buildNavPlan(baseDoc, pages, [], new Map(), context) : null;
|
|
328
475
|
for (const n of notes)
|
|
329
476
|
report.notes.push(n);
|
|
330
477
|
const { renames } = placePages({
|
|
@@ -357,11 +504,15 @@ export function syncNavigatorTree(root, routesDir, pagesDir, pages, pagesMap, ba
|
|
|
357
504
|
// ---- layouts: CLI-owned, regenerated (with local renames reflected) ----
|
|
358
505
|
const segmentOverrides = new Map(plan.routes.map((r) => [r.routeId, r.segment]));
|
|
359
506
|
const mergedDoc = applyRenames(remoteDoc, renameMap);
|
|
360
|
-
const
|
|
507
|
+
const finalPlan = buildNavPlan(mergedDoc, pages, [], segmentOverrides, context);
|
|
508
|
+
const finalLayouts = finalPlan.layouts;
|
|
361
509
|
for (const layout of finalLayouts) {
|
|
362
510
|
writeOwnedLayout(root, routesDir, layout.file, layout.content, report);
|
|
363
511
|
}
|
|
364
512
|
pruneStaleLayouts(root, routesDir, new Set(finalLayouts.map((l) => l.file)), report);
|
|
513
|
+
// The document the layouts were generated from, and where each route's
|
|
514
|
+
// screen is: what the app runtime navigates, guards and enters by.
|
|
515
|
+
writeNavigationModule(root, mergedDoc, finalPlan.screensByRoute, report);
|
|
365
516
|
// ---- push: replace each renamed navigator's routes array wholesale ----
|
|
366
517
|
const pushOps = [];
|
|
367
518
|
// Only a rename that changes the document is worth a commit; a no-op patch
|