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.
Files changed (67) hide show
  1. package/dist/api/client.d.ts +15 -0
  2. package/dist/api/client.js +36 -0
  3. package/dist/api/client.js.map +1 -1
  4. package/dist/api/commit.js +19 -1
  5. package/dist/api/commit.js.map +1 -1
  6. package/dist/api/errors.d.ts +1 -1
  7. package/dist/api/errors.js +3 -0
  8. package/dist/api/errors.js.map +1 -1
  9. package/dist/api/objects.d.ts +3 -1
  10. package/dist/api/objects.js +31 -4
  11. package/dist/api/objects.js.map +1 -1
  12. package/dist/api/project.d.ts +11 -0
  13. package/dist/api/project.js +29 -0
  14. package/dist/api/project.js.map +1 -0
  15. package/dist/assets/assets.d.ts +12 -0
  16. package/dist/assets/assets.js +31 -2
  17. package/dist/assets/assets.js.map +1 -1
  18. package/dist/commands/doctor.js +2 -1
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/impl/createImpl.js +26 -4
  21. package/dist/commands/impl/createImpl.js.map +1 -1
  22. package/dist/commands/impl/doctorImpl.js +14 -7
  23. package/dist/commands/impl/doctorImpl.js.map +1 -1
  24. package/dist/commands/impl/initImpl.js +34 -1
  25. package/dist/commands/impl/initImpl.js.map +1 -1
  26. package/dist/commands/init.js +1 -0
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/nav/navigatorTree.d.ts +36 -2
  29. package/dist/nav/navigatorTree.js +191 -40
  30. package/dist/nav/navigatorTree.js.map +1 -1
  31. package/dist/nav/routeTree.d.ts +2 -1
  32. package/dist/nav/routeTree.js +9 -4
  33. package/dist/nav/routeTree.js.map +1 -1
  34. package/dist/nav/screenOptions.d.ts +24 -0
  35. package/dist/nav/screenOptions.js +41 -13
  36. package/dist/nav/screenOptions.js.map +1 -1
  37. package/dist/runtime/runtimeFiles.d.ts +17 -0
  38. package/dist/runtime/runtimeFiles.js +183 -0
  39. package/dist/runtime/runtimeFiles.js.map +1 -0
  40. package/dist/services/appDeps.d.ts +32 -0
  41. package/dist/services/appDeps.js +132 -0
  42. package/dist/services/appDeps.js.map +1 -0
  43. package/dist/services/doctorService.d.ts +19 -1
  44. package/dist/services/doctorService.js +117 -3
  45. package/dist/services/doctorService.js.map +1 -1
  46. package/dist/services/scaffold.d.ts +13 -1
  47. package/dist/services/scaffold.js +57 -5
  48. package/dist/services/scaffold.js.map +1 -1
  49. package/dist/services/syncEngine.js +85 -22
  50. package/dist/services/syncEngine.js.map +1 -1
  51. package/dist/types.d.ts +4 -0
  52. package/dist/types.js.map +1 -1
  53. package/dist/version.d.ts +21 -0
  54. package/dist/version.js +40 -3
  55. package/dist/version.js.map +1 -1
  56. package/package.json +4 -3
  57. package/templates/agent/mapples.md +9 -4
  58. package/templates/agent/skills/mapples-conventions/SKILL.md +2 -2
  59. package/templates/agent/skills/mapples-design/SKILL.md +4 -4
  60. package/templates/agent/skills/mapples-design/reference/component-library.md +1 -1
  61. package/templates/agent/skills/mapples-design/reference/layout-contract.md +2 -3
  62. package/templates/agent/skills/mapples-design/reference/playbooks/onboarding-flow.md +1 -1
  63. package/templates/agent/skills/mapples-design/reference/playbooks/screen-flow-wiring.md +2 -5
  64. package/templates/app/_layout.tsx +6 -1
  65. package/templates/gitignore +2 -0
  66. package/templates/metro.config.js +11 -0
  67. 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;AAElE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAA6B;IAC3D,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAEvD,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC;YAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACjE,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,YAAY,sCAAsC,CAAC,CAAC,CAAC;QACpF,OAAO;IACT,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzE,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,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC;QAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACnE,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 { ensureBabelPlugin, writeSidTypes } from '../../services/scaffold.js';
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":"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"}
1
+ {"version":3,"file":"initImpl.js","sourceRoot":"","sources":["../../../src/commands/impl/initImpl.ts"],"names":[],"mappings":"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"}
@@ -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
- export declare function buildNavPlan(doc: NavigatorDoc, pages: Map<string, PageEntity>, notes: string[], segmentOverrides?: Map<string, string>): NavPlan;
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
- export function buildNavPlan(doc, pages, notes, segmentOverrides = new Map()) {
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.replace(/^\/+/, '').replace(/\/+$/, '') || 'index';
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 options = mapScreenOptions(route, nav.type, where, notes);
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 root = walk(doc.rootNavigatorId, '', false, rootUsed);
162
- if (!root)
163
- return { ...makePlan(routes), layouts, navigatorByDir, detachedByDir };
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: dir ? join(dir, '_layout.tsx') : '_layout.tsx',
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
- /** Options literal; a string `tabBarIcon`/`drawerIcon` becomes an Icon renderer. */
210
- function optionsLiteral(options, usesIcon) {
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
- usesIcon.value = true;
217
- iconParts.push(`${key}: ({ color }) => <Icon iconName=${JSON.stringify(value)} color={color} />`);
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
- function layoutContent(nav, screens, notes) {
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 usesIcon = { value: false };
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, usesIcon)}} />`;
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
- ...(usesIcon.value ? ["import { Icon } from '@mapples/ui';"] : []),
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
- ` <${kind.name}${screenOptions}>`,
253
- ...screenLines,
254
- ` </${kind.name}>`,
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
- if (existsSync(abs)) {
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({ file: rel, action: 'updated', detail: 'CLI-owned layout' });
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 plan = remoteDoc ? buildNavPlan(remoteDoc, pages, notes) : makePlan([]);
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 finalLayouts = buildNavPlan(mergedDoc, pages, [], segmentOverrides).layouts;
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