@tinycld/bootstrap 2.4.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,9 @@ pnpm install # links members + runs the generator (postinstall)
23
23
  cd tinycld && pnpm run dev
24
24
  ```
25
25
 
26
- The CLI writes the workspace coordination files (`package.json`, `tinycld.packages.ts`, `vitest.config.ts`, shared test stubs) from embedded templates, then clones the `tinycld` repo (which carries `@tinycld/core` + `@tinycld/package-scripts` nested) as a sibling. Each `--with <slug>` adds one feature sibling. `tinycld` is always cloned; everything else is opt-in.
26
+ The CLI writes a small set of workspace files from embedded templates (`tinycld.packages.ts`, `vitest.config.ts`, shared test stubs), then clones the `tinycld` repo (which carries `@tinycld/core` + `@tinycld/package-scripts` nested) as a sibling. Each `--with <slug>` adds one feature sibling. `tinycld` is always cloned; everything else is opt-in.
27
+
28
+ Bootstrap carries no vendor version pins and owns none of the root coordination-file formats. Immediately after cloning, it runs `scripts/write-workspace-root.ts` **from inside the freshly cloned `tinycld` repo** to write `package.json`, `pnpm-workspace.yaml` (incl. the pnpm `overrides:` block), a root `package-versions.json` copy, `biome.json`, `.watchmanconfig`, and `.npmrc` — all derived from `tinycld/core/package-versions.json`, the pins' source of truth. This means `--assemble-only` requires a tinycld ref that contains `scripts/write-workspace-root.ts` (the first tinycld release after 2026-08); an older ref fails assembly with a clear error naming the missing script, rather than silently producing a workspace with stale or missing root files.
27
29
 
28
30
  `--with name@ref` pins a clone to a tag, branch, or commit:
29
31
 
@@ -47,7 +49,7 @@ TINYCLD_REPO_BASE=https://github.com/tinycld \
47
49
  npx @tinycld/bootstrap --new my-feature
48
50
  ```
49
51
 
50
- You'll be walked through an interactive prompt. The positional argument (`my-feature`) is the **slug** — kebab-case, 3–40 chars, becomes `@tinycld/my-feature`, the URL segment `/a/<orgSlug>/my-feature/`, and the Go module `tinycld.org/packages/my-feature`. Leave it off to be asked for it.
52
+ You'll be walked through an interactive prompt. The positional argument (`my-feature`) is the **slug** — kebab-case, 3–40 chars, becomes `@tinycld/my-feature`, the URL segment `/a/my-feature/`, and the Go module `tinycld.org/packages/my-feature`. Leave it off to be asked for it.
51
53
 
52
54
  If `--new` runs from inside an existing workspace root (a `tinycld/` shell sibling detected), the new package is scaffolded as a sibling and the link step adds it to `pnpm-workspace.yaml`. Otherwise the CLI creates a wrapper directory `./tinycld-<slug>/` with the package at `./tinycld-<slug>/<slug>/`, assembles a workspace around it (cloning the `tinycld` repo), and links — leaving you a self-contained, runnable workspace.
53
55
 
@@ -131,7 +133,7 @@ my-feature/
131
133
  ├── sidebar.tsx # sidebar rendered for this package's routes
132
134
  ├── types.ts # MyFeatureSchema + record interfaces
133
135
  └── screens/
134
- ├── _layout.tsx # Stack layout for /a/[orgSlug]/my-feature/**
136
+ ├── _layout.tsx # Stack layout for /a/my-feature/**
135
137
  ├── [id].tsx # detail route
136
138
  └── index.tsx # list route
137
139
  ```
@@ -142,7 +144,7 @@ The `tinycld/my-feature/` nesting gives the package a stable public API surface
142
144
 
143
145
  ### `settings-only` — service package
144
146
 
145
- Matches `@tinycld/google-takeout-import`. The package contributes only a settings panel — no routes, no nav entry, no collections, no server. Use this for integrations or admin-style tools that live under `/a/<orgSlug>/settings/**`.
147
+ Matches `@tinycld/google-takeout-import`. The package contributes only a settings panel — no routes, no nav entry, no collections, no server. Use this for integrations or admin-style tools that live under `/a/settings/**`.
146
148
 
147
149
  <details>
148
150
  <summary>Generated tree</summary>
@@ -172,7 +174,7 @@ The scaffolded `manifest.ts` is the single source of truth for what a package co
172
174
  | Field | Meaning |
173
175
  |---|---|
174
176
  | `name`, `slug`, `version`, `description` | Identity. `slug` is the URL segment and the npm name's last segment. |
175
- | `routes.directory` | Subpath (resolved through `package.json` exports) where org-scoped screens live. Generator re-exports each screen file under `app/a/[orgSlug]/<slug>/`. |
177
+ | `routes.directory` | Subpath (resolved through `package.json` exports) where the package's screens live. Generator re-exports each screen file under `app/a/(app)/<slug>/`. |
176
178
  | `nav` | `{ label, icon, order, shortcut }` — sidebar entry for the org workspace. |
177
179
  | `sidebar.component` | Subpath to the package's sidebar component, rendered when on its routes. |
178
180
  | `provider.component` | Optional context provider mounted around the package's routes. |
@@ -180,7 +182,7 @@ The scaffolded `manifest.ts` is the single source of truth for what a package co
180
182
  | `collections.register`, `collections.types` | Subpaths to the pbtsdb registration function and the schema types. |
181
183
  | `seed.script` | Default-export async function called by the dev seeder for this package. |
182
184
  | `server` | `{ package, module }` — relative dir + Go module path for the optional server extension. |
183
- | `settings[]` | One entry per panel contributed under `/a/<orgSlug>/settings/<slug>`. |
185
+ | `settings[]` | One entry per panel contributed under `/a/settings/<slug>`. |
184
186
 
185
187
  All path-shaped fields use **short subpaths** (`'screens'`, `'sidebar'`, `'collections'`) that match the keys in `package.json`'s `exports` map — the generator follows the exports map to find the actual files under `tinycld/<slug>/...`.
186
188
 
@@ -206,7 +208,7 @@ cd my-feature
206
208
  pnpm exec tinycld-pkg check
207
209
  ```
208
210
 
209
- Once linked, the app shell's generator wires your manifest in automatically: routes appear at `/a/<orgSlug>/my-feature/**`, the sidebar renders, the settings panel shows up, migrations get picked up by PocketBase. No further changes to the `tinycld` shell or `@tinycld/core` are needed.
211
+ Once linked, the app shell's generator wires your manifest in automatically: routes appear at `/a/my-feature/**`, the sidebar renders, the settings panel shows up, migrations get picked up by PocketBase. No further changes to the `tinycld` shell or `@tinycld/core` are needed.
210
212
 
211
213
  > ⚠️ **`tinycld/metro.config.cjs` watches the workspace root**, but Expo's resolver caches package metadata at boot. If you add a new sibling while `pnpm run dev` is already running, restart it (Ctrl-C, then `pnpm run dev`) so the new member is picked up. CI is fine — it always starts fresh.
212
214
 
@@ -1,412 +1,46 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
3
3
  import { dirname, join, relative } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  // All feature members that exist in the ecosystem. The workspace manifest lists
6
6
  // ALL of them (npm tolerates absent dirs), so a partial checkout still installs.
7
7
  // But --assemble-only CLONES only app+core+requested — we do NOT force-clone all.
8
- const ALL_FEATURES = ['contacts', 'mail', 'calendar', 'drive', 'calc', 'text', 'google-takeout-import'];
8
+ const ALL_FEATURES = [
9
+ 'contacts',
10
+ 'mail',
11
+ 'calendar',
12
+ 'drive',
13
+ 'calc',
14
+ 'text',
15
+ 'google-takeout-import',
16
+ 'boards',
17
+ ];
9
18
  // The `tinycld` member is the one always-cloned repo (tinycld/tinycld): it is
10
19
  // the merged app shell + core. assembleWorkspace seeds it directly, pinnable via
11
20
  // tinycldRef. Both @tinycld/core (at tinycld/core) and @tinycld/package-scripts
12
21
  // (the tinycld-pkg CLI, at tinycld/package-scripts) live NESTED inside it, so
13
22
  // they arrive with the single clone and are never cloned separately.
14
- // Listed in the manifest workspaces array (everything that could exist). The
15
- // nested member paths tinycld/core and tinycld/package-scripts are valid npm
16
- // workspace entries that resolve inside the one cloned `tinycld` repo.
17
- const ALL_MEMBERS = ['tinycld', 'tinycld/core', 'tinycld/package-scripts', ...ALL_FEATURES];
18
- // pnpm version pinned via the package.json "packageManager" field so corepack
19
- // resolves the same pnpm everywhere (local, CI, EAS). Bump in lockstep with the
20
- // workspace's committed packageManager.
21
- const PNPM_VERSION = '11.3.0';
22
- // Framework / native / styling version pins, written to package-versions.json and
23
- // transcribed into the pnpm `overrides:` block. The in-app OTA rebuild installs
24
- // with `pnpm install --no-frozen-lockfile` (admin package editing changes the
25
- // FEATURE set, so the lockfile can't be frozen), which would otherwise re-resolve
26
- // every caret/tilde range and drift these deps past the device's embedded native
27
- // binary — a native-module mismatch breaks the OTA bundle, a uniwind/tailwindcss
28
- // bump recompiles classNames differently (app-wide visual drift). Overrides force
29
- // the embedded-binary versions regardless of --no-frozen-lockfile while leaving
30
- // feature packages free. Bump in lockstep with each native (EAS) build.
31
- const FRAMEWORK_OVERRIDES = {
32
- expo: '55.0.26',
33
- react: '19.2.0',
34
- 'react-dom': '19.2.0',
35
- 'react-native': '0.83.6',
36
- 'react-native-reanimated': '4.2.1',
37
- 'react-native-worklets': '0.7.4',
38
- 'react-native-gesture-handler': '3.0.1',
39
- 'react-native-safe-area-context': '5.6.2',
40
- 'react-native-screens': '4.23.0',
41
- 'react-native-svg': '15.15.3',
42
- 'react-native-web': '0.21.2',
43
- 'expo-router': '55.0.16',
44
- 'expo-image': '55.0.11',
45
- 'expo-image-picker': '55.0.20',
46
- '@sentry/react-native': '7.11.0',
47
- uniwind: '1.8.0',
48
- tailwindcss: '4.3.0',
49
- 'tailwind-variants': '0.1.20',
50
- 'tailwind-merge': '3.6.0',
51
- '@gluestack-ui/utils': '5.0.5-alpha.0',
52
- '@shopify/flash-list': '2.0.2',
53
- 'lucide-react-native': '1.17.0',
54
- 'react-hook-form': '7.77.0',
55
- '@tanstack/db': '0.8.5',
56
- // Must move in lockstep with @tanstack/db above: react-db calls internals of
57
- // a specific db version, so pinning one and letting the other float produces
58
- // a workspace that installs clean and throws on first render
59
- // ("(0, t.isCollection) is not a function" — every screen dead).
60
- '@tanstack/react-db': '0.3.5',
61
- // Same lockstep, and it is NOT optional just because nothing imports it
62
- // directly: query-db-collection reaches into @tanstack/db's internals
63
- // (getLoadSubsetDemandKey). Left unpinned it floats to a release built
64
- // against a newer db than the pin above, and the workspace dies at import
65
- // with "does not provide an export named 'getLoadSubsetDemandKey'" — which
66
- // is exactly how it failed in CI while every developer's machine, holding
67
- // an older resolved copy, stayed green.
68
- '@tanstack/query-db-collection': '1.2.10',
69
- '@tanstack/react-query': '5.101.0',
70
- // Not a version pin: a git fork. Upstream drax has unmerged fixes the board
71
- // drag depends on, so every workspace must resolve to the fork or the
72
- // kanban drag regresses. The fork commits its built lib/ and declares no
73
- // prepare script, so this needs no allowBuilds entry — pnpm only blocks git
74
- // deps that would run a build script on install.
75
- 'react-native-drax': 'github:nathanstitt/react-native-drax#b863d89a70c73454d7f3495bf79e382586495fa4',
76
- };
77
- // The documentation key carried in package-versions.json (ignored by the YAML
78
- // generators — it isn't a valid package name). Kept in sync with the Go reader's
79
- // delete("//") in rebuild_assemble.go.
80
- const OVERRIDES_DOC = 'Source of truth for the pnpm `overrides:` block in pnpm-workspace.yaml. Both generators (this file and the Go writePnpmWorkspaceYAML in the OTA rebuild) read these pins so the dev workspace and the OTA build pin identically. See FRAMEWORK_OVERRIDES in assemble-workspace.ts for why. Bump in lockstep with each native (EAS) build.';
81
- // renderOverridesBlock formats FRAMEWORK_OVERRIDES as a pnpm `overrides:` YAML
82
- // block, sorted for a stable diff. Scoped names (leading @) are single-quoted so
83
- // YAML doesn't read them as anchors; plain names are bare. Mirrors the Go
84
- // renderOverridesBlock so both generators emit byte-identical blocks.
85
- function renderOverridesBlock() {
86
- const lines = ['overrides:'];
87
- for (const name of Object.keys(FRAMEWORK_OVERRIDES).sort()) {
88
- const key = name.startsWith('@') ? `'${name}'` : name;
89
- lines.push(` ${key}: ${FRAMEWORK_OVERRIDES[name]}`);
90
- }
91
- return lines.join('\n');
92
- }
93
23
  /**
94
- * Direct child dirs of `root` that look like a feature member already on disk:
95
- * a package.json plus a manifest.ts/js. This mirrors the app generator's own
96
- * discovery (it scans the workspace root for manifest-bearing members), so the
97
- * member list bootstrap writes stays in sync with what the generator will load.
98
- *
99
- * The motivating case is CI / a custom package: a member checked out into its
100
- * slot but absent from ALL_MEMBERS is discovered by the generator yet never
101
- * linked by pnpm (→ "No manifest found"). Self-registering it here closes that
102
- * gap without requiring every package to be hardcoded in ALL_FEATURES.
24
+ * Run the workspace root-writer that ships INSIDE the cloned tinycld repo.
25
+ * Bootstrap deliberately knows nothing about the root files' contents — not
26
+ * the vendor version pins (tinycld/core/package-versions.json) and not the
27
+ * file formats. A vendor bump or format change is a tinycld commit, never a
28
+ * bootstrap release. Node >=24 (our engines floor) executes the TypeScript
29
+ * directly via native type stripping; the script is self-contained by
30
+ * contract, so no install has to have happened yet.
103
31
  */
104
- function discoverPresentMembers(root) {
105
- let entries;
106
- try {
107
- entries = readdirSync(root);
32
+ function delegateWorkspaceRoot(root) {
33
+ const script = join(root, 'tinycld', 'scripts', 'write-workspace-root.ts');
34
+ const tinycldDir = join(root, 'tinycld');
35
+ if (!existsSync(script)) {
36
+ throw new Error(`${script} not found — the tinycld checkout at ${tinycldDir} predates core-owned ` +
37
+ 'workspace roots — update it (git pull) or pass a newer tinycldRef; or use an ' +
38
+ 'older @tinycld/bootstrap that still writes root files itself.');
108
39
  }
109
- catch {
110
- return [];
40
+ const r = spawnSync(process.execPath, [script], { cwd: root, stdio: 'inherit' });
41
+ if (r.status !== 0) {
42
+ throw new Error(`write-workspace-root failed (exit ${r.status ?? 'signal'}) — see output above`);
111
43
  }
112
- return entries.filter((name) => {
113
- if (name === 'node_modules' || name.startsWith('.'))
114
- return false;
115
- const dir = join(root, name);
116
- try {
117
- if (!statSync(dir).isDirectory())
118
- return false;
119
- }
120
- catch {
121
- return false;
122
- }
123
- const hasManifest = existsSync(join(dir, 'manifest.ts')) || existsSync(join(dir, 'manifest.js'));
124
- return existsSync(join(dir, 'package.json')) && hasManifest;
125
- });
126
- }
127
- /**
128
- * pnpm-workspace.yaml contents: member discovery list + the settings the
129
- * ecosystem depends on. node-linker=hoisted reproduces npm's flat node_modules
130
- * so feature siblings (peerDeps-only, no node_modules of their own) resolve
131
- * react/expo/etc. up to the root, and Metro/tsc resolution work unchanged.
132
- * pnpm 10+ reads these keys from THIS file, not .npmrc.
133
- *
134
- * `dir` is the workspace root: any manifest-bearing member already on disk but
135
- * not in ALL_MEMBERS (a CI- or custom-package checkout) is unioned into the
136
- * `packages:` list so pnpm links what the generator discovers. This list is the
137
- * source of truth pnpm reads — unlike the package.json `workspaces` hint.
138
- */
139
- function pnpmWorkspaceYaml(dir) {
140
- const allMembers = [...new Set([...ALL_MEMBERS, ...discoverPresentMembers(dir)])];
141
- const members = allMembers.map((m) => ` - ${m}`).join('\n');
142
- return [
143
- 'nodeLinker: hoisted',
144
- 'linkWorkspacePackages: true',
145
- 'strictPeerDependencies: false',
146
- 'enablePrePostScripts: true',
147
- '',
148
- '# pnpm 11 ships a default minimumReleaseAge supply-chain gate (~24h) that',
149
- '# rejects very freshly-published versions. The @tinycld/* libraries are',
150
- '# first-party and released in lockstep with these members, so a same-day',
151
- '# pbtsdb (or other @tinycld dep) bump must install immediately rather than',
152
- '# blocking install/CI for a day. Exclude them from the gate; third-party',
153
- '# packages still get the freshness window.',
154
- 'minimumReleaseAgeExclude:',
155
- ' - pbtsdb',
156
- " - '@tinycld/*'",
157
- '',
158
- 'packages:',
159
- members,
160
- '',
161
- '# Build-script approvals (pnpm blocks dependency build scripts by default).',
162
- 'allowBuilds:',
163
- ' esbuild: true',
164
- " '@sentry/cli': true",
165
- '',
166
- '# Framework/native/styling version pins — see package-versions.json (the',
167
- '# source of truth) and FRAMEWORK_OVERRIDES in @tinycld/bootstrap. These keep',
168
- "# the OTA rebuild's --no-frozen-lockfile install from drifting these deps off",
169
- '# the embedded native binary.',
170
- renderOverridesBlock(),
171
- '',
172
- ].join('\n');
173
- }
174
- /**
175
- * .watchmanconfig contents: the directories watchman must NOT crawl or subscribe
176
- * to. The motivating failure is native build artifacts — tinycld/ios (Pods) and
177
- * tinycld/android (.gradle, .cxx, build/) are multi-GB and churn on every native
178
- * build, overflowing the fsevents kernel queue ("MustScanSubDirs UserDropped")
179
- * and forcing slow recrawls that stall Metro starts.
180
- *
181
- * CRITICAL: the root node_modules is NOT ignored. Under node-linker=hoisted it's
182
- * the flat store Metro resolves EVERY dependency from (incl. expo-router/entry,
183
- * the app entry point); hiding it from watchman makes Metro's file map miss those
184
- * files → "Unable to resolve module ./node_modules/...". Only node_modules/.cache
185
- * (Metro's transform cache, never a resolvable path) is excluded. Each MEMBER's
186
- * own node_modules IS ignored — under hoisting those are near-empty (deps resolve
187
- * up to the root store), so Metro never reads them and they're pure crawl cost.
188
- *
189
- * Ignore_dirs matches path prefixes relative to the watch root, so the bare
190
- * ".git" covers only the root-level one and every member's .git is listed
191
- * explicitly. Listing an ignore_dir that doesn't exist is harmless — watchman
192
- * skips absent paths.
193
- *
194
- * NON_MEMBER_DIRS are sibling dirs that aren't workspace members (so they're
195
- * absent from the member union above) but routinely exist in a dev checkout and
196
- * carry their own node_modules/.git: bootstrap (cloned for development), and the
197
- * utils/web ecosystem tooling. Their node_modules/.git/dist are excluded too —
198
- * none are part of the Metro app graph.
199
- */
200
- const NON_MEMBER_DIRS = ['bootstrap', 'utils', 'web'];
201
- function watchmanConfig(dir) {
202
- const members = [...new Set([...ALL_MEMBERS, ...discoverPresentMembers(dir)])];
203
- const memberDirs = members.flatMap((m) => [`${m}/node_modules`, `${m}/.git`]);
204
- const testResultDirs = members.filter((m) => !m.startsWith('tinycld')).map((m) => `${m}/test-results`);
205
- const nonMemberDirs = NON_MEMBER_DIRS.flatMap((d) => [`${d}/node_modules`, `${d}/.git`, `${d}/dist`]);
206
- const ignoreDirs = [
207
- // Root-level .git only. The root node_modules is deliberately NOT ignored:
208
- // under node-linker=hoisted it's the flat store Metro resolves every dep
209
- // from (incl. expo-router/entry), so hiding it breaks module resolution.
210
- '.git',
211
- // Metro's own transform cache under the root store — churns every build,
212
- // never a resolvable module path.
213
- 'node_modules/.cache',
214
- ...memberDirs,
215
- ...nonMemberDirs,
216
- // App-shell native + generated output that churns on builds. ios/android
217
- // are multi-GB and the original motivation for this file.
218
- 'tinycld/ios',
219
- 'tinycld/android',
220
- 'tinycld/modules/app-updater/android/build',
221
- 'tinycld/modules/app-updater/ios/build',
222
- 'tinycld/.expo',
223
- 'tinycld/dist',
224
- 'tinycld/server/pb_data',
225
- 'tinycld/server/pb_test_data',
226
- 'tinycld/test-results',
227
- 'tinycld/playwright-report',
228
- ...testResultDirs,
229
- ];
230
- const config = {
231
- enable_parallel_crawl: true,
232
- // fsevents_try_resync is FALSE — watchman's own default since Dec 2021.
233
- // When fsevents drops events (kFSEventStreamEventFlagUserDropped, common
234
- // under the ~110k-file root store on a `pnpm install` / `git checkout`
235
- // burst), resync-from-journal usually fails on macOS and falls back to a
236
- // full recrawl — that's the 60s "syncToNow: timed out waiting for cookie
237
- // file" stall on Metro start. Leaving it false skips the doomed resync.
238
- fsevents_try_resync: false,
239
- // Latency passed to FSEventStreamCreate (seconds). Higher = the OS batches
240
- // more change notifications per wakeup, so a burst overflows the kernel
241
- // queue less. Measured on a fast SSD here, an 8k-file burst (a `pnpm
242
- // install` / `git checkout`) still drops events at 0.1/0.25, goes clean at
243
- // 0.5, and survives even a 20k-file burst at 1.0. We ship 1.0 because this
244
- // default lands on unknown hardware: a slow / encrypted / cloud-synced
245
- // volume drains the fsevents queue more slowly, so it drops at a FAR
246
- // smaller burst than an SSD — the extra latency is the margin those
247
- // machines need. The cost is ~1s of detection lag before HMR fires per
248
- // save; on a slow disk (where rebuilds are already slower) that's
249
- // proportionally minor, and stability beats shaving it. (default is 0.01.)
250
- fsevents_latency: 1.0,
251
- ignore_dirs: [...new Set(ignoreDirs)],
252
- };
253
- return `${JSON.stringify(config, null, 4)}\n`;
254
- }
255
- /**
256
- * Write (or merge into) the workspace-root coordination files in `dir`:
257
- * package.json, pnpm-workspace.yaml, and scripts/link-members.ts.
258
- *
259
- * The workspace is a pnpm workspace. Member discovery + pnpm settings live in
260
- * pnpm-workspace.yaml (always rewritten — bootstrap owns the member list).
261
- * package.json carries the pinned packageManager, the tsx devDep the postinstall
262
- * needs, and the postinstall script (link-members + generator). When a
263
- * package.json already exists (real workspace checkout), its other fields are
264
- * preserved and only the bootstrap-owned bits are enforced.
265
- *
266
- * A legacy npm `workspaces` array is also written as a monorepo-detection HINT
267
- * for external tooling (EAS/expo archiver keys off it, not pnpm-workspace.yaml).
268
- * pnpm ignores it when pnpm-workspace.yaml is present.
269
- */
270
- export function writeWorkspaceManifest(dir) {
271
- const pkgPath = join(dir, 'package.json');
272
- let existing = {};
273
- if (existsSync(pkgPath)) {
274
- try {
275
- existing = JSON.parse(readFileSync(pkgPath, 'utf-8'));
276
- }
277
- catch {
278
- // Unparseable existing file — start from defaults
279
- }
280
- }
281
- const existingScripts = typeof existing.scripts === 'object' && existing.scripts !== null
282
- ? existing.scripts
283
- : {};
284
- const existingDevDeps = typeof existing.devDependencies === 'object' && existing.devDependencies !== null
285
- ? existing.devDependencies
286
- : {};
287
- const pkg = {
288
- name: '@tinycld/workspace',
289
- version: '0.0.0',
290
- private: true,
291
- type: 'module',
292
- ...existing,
293
- packageManager: `pnpm@${PNPM_VERSION}`,
294
- // Monorepo-detection hint for external tooling (see doc comment); pnpm
295
- // itself reads members from pnpm-workspace.yaml and ignores this. Union
296
- // in any manifest-bearing member already on disk so the hint matches the
297
- // authoritative pnpm-workspace.yaml member list.
298
- workspaces: [...new Set([...ALL_MEMBERS, ...discoverPresentMembers(dir)])],
299
- scripts: {
300
- ...existingScripts,
301
- // link-members runs FIRST so the @tinycld/<member> symlinks (notably
302
- // @tinycld/core) exist before the generator's package-build step
303
- // (text's webview-editor build.ts does a bare `import '@tinycld/core/...'`
304
- // resolved by package name, which fails on a CLEAN install if the
305
- // symlink isn't there yet). It then runs AGAIN after generate to add
306
- // the @tinycld/app-generated link (whose target, tinycld/lib/generated,
307
- // only exists once generate has run); link-members is idempotent and
308
- // skips app-generated when absent, so the first pass is harmless and
309
- // the second completes the graph. All app scripts run from tinycld/.
310
- postinstall: 'tsx scripts/link-members.ts && cd tinycld && pnpm run packages:generate && cd .. && tsx scripts/link-members.ts && cd tinycld && pnpm run assets:copy-pdfjs',
311
- },
312
- devDependencies: {
313
- ...existingDevDeps,
314
- tsx: '^4.21.0',
315
- },
316
- };
317
- writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 4)}\n`);
318
- // pnpm-workspace.yaml is the source of truth for members + settings — always
319
- // rewrite it (unlike package.json, no human-owned fields live here).
320
- writeFileSync(join(dir, 'pnpm-workspace.yaml'), pnpmWorkspaceYaml(dir));
321
- // package-versions.json holds the version pins transcribed into the YAML
322
- // `overrides:` block above. It's a standalone file (not just inline in the
323
- // YAML) because the Go OTA-rebuild generator reads it to emit the same block
324
- // on the server, where bootstrap never runs — so dev and OTA pin identically.
325
- // Always rewritten: the pins are bootstrap-owned, no human fields live here.
326
- const overridesJson = { '//': OVERRIDES_DOC, ...FRAMEWORK_OVERRIDES };
327
- writeFileSync(join(dir, 'package-versions.json'), `${JSON.stringify(overridesJson, null, 4)}\n`);
328
- // .watchmanconfig is derived from the member list, like pnpm-workspace.yaml —
329
- // always rewrite so it self-heals and can't drift to a stale ignore list (a
330
- // hand-maintained one kept missing the hoisted root node_modules, which is
331
- // what stalls Metro starts). No human-owned fields live here.
332
- writeFileSync(join(dir, '.watchmanconfig'), watchmanConfig(dir));
333
- // Minimal .npmrc: all pnpm settings live in pnpm-workspace.yaml (pnpm 10+
334
- // reads them there, not from .npmrc). Only written when absent (don't
335
- // clobber a real checkout's).
336
- const npmrcPath = join(dir, '.npmrc');
337
- if (!existsSync(npmrcPath)) {
338
- writeFileSync(npmrcPath, '# pnpm settings live in pnpm-workspace.yaml (pnpm 10+ reads them there).\n');
339
- }
340
- writeRootBiomeConfig(dir);
341
- }
342
- // Re-root a biome glob written against the canonical's dir (<root>/tinycld/) so
343
- // it means the same thing one level up at the workspace root. `**`-anchored globs
344
- // already match at any depth; only bare relative segments need the tinycld/ prefix.
345
- function rerootBiomeGlob(glob) {
346
- const negated = glob.startsWith('!');
347
- const body = negated ? glob.slice(1) : glob;
348
- if (body.startsWith('**') || body.startsWith('/'))
349
- return glob;
350
- const rerooted = `tinycld/${body}`;
351
- return negated ? `!${rerooted}` : rerooted;
352
- }
353
- function rerootBiomePluginPath(p) {
354
- if (p.startsWith('/'))
355
- return p;
356
- return `./tinycld/${p.replace(/^\.\//, '')}`;
357
- }
358
- /**
359
- * Write the workspace-root biome.json. Biome searches only UPWARD for config,
360
- * and the canonical biome.json lives at <root>/tinycld/ — a SIBLING of the
361
- * feature members, never an ancestor. Without a `root: true` config above the
362
- * members, running biome from inside a sibling (or via the editor/LSP) finds
363
- * nothing and falls back to biome's built-in defaults, flooding output with
364
- * bogus reformatting.
365
- *
366
- * This config must NOT use `extends: ['./tinycld/biome.json']`: Biome 2.5.0
367
- * silently DROPS the entire `plugins` array from any config reached through a
368
- * file-path `extends` (biome #8488, only partially fixed by #8524, which covers
369
- * `extends: "//"` but not a root extending a sibling file). Rules still load, so
370
- * the GritQL pbtsdb guards would just never run. So the root config must INLINE
371
- * the canonical's rules + plugins. The generator (tinycld/scripts/generate.ts,
372
- * run by every install's postinstall) is the authoritative writer and re-derives
373
- * this file from the canonical each install; this seed only has to be correct for
374
- * the window between `--assemble-only` and the first install.
375
- *
376
- * Bootstrap can only inline when the canonical is already on disk (a re-assemble
377
- * onto an existing workspace, or after the tinycld clone). On a fresh assemble the
378
- * tinycld repo isn't cloned yet, so we fall back to a minimal `root: true` seed
379
- * with no plugins — harmless because nothing is installed to lint yet, and the
380
- * first install's generator immediately replaces it with the full inlined config.
381
- *
382
- * `files.includes`/plugin paths are re-rooted (the canonical writes them relative
383
- * to tinycld/; inlining moves them up one dir). `vcs.root` points at tinycld/, not
384
- * the workspace root: the canonical relies on tinycld/.gitignore to exclude
385
- * generated/build artifacts, and every invocation under the workspace root
386
- * resolves THIS config as the root and inherits its vcs settings. A fresh
387
- * workspace root has no .gitignore (and isn't a git repo), so anchoring vcs there
388
- * would error "couldn't find an ignore file".
389
- */
390
- function writeRootBiomeConfig(root) {
391
- const canonicalPath = join(root, 'tinycld', 'biome.json');
392
- const config = existsSync(canonicalPath)
393
- ? (() => {
394
- const canonical = JSON.parse(readFileSync(canonicalPath, 'utf8'));
395
- const c = { ...canonical, root: true };
396
- delete c.extends;
397
- if (c.files?.includes) {
398
- c.files = { ...c.files, includes: c.files.includes.map(rerootBiomeGlob) };
399
- }
400
- if (Array.isArray(c.plugins)) {
401
- c.plugins = c.plugins.map((entry) => typeof entry === 'string'
402
- ? rerootBiomePluginPath(entry)
403
- : { ...entry, path: rerootBiomePluginPath(entry.path) });
404
- }
405
- return c;
406
- })()
407
- : { $schema: 'https://biomejs.dev/schemas/2.5.0/schema.json', root: true };
408
- config.vcs = { enabled: true, clientKind: 'git', useIgnoreFile: true, root: 'tinycld' };
409
- writeFileSync(join(root, 'biome.json'), `${JSON.stringify(config, null, 4)}\n`);
410
44
  }
411
45
  /**
412
46
  * Resolve the workspace-template dir relative to this module. Published builds
@@ -466,13 +100,17 @@ function realClone(url, dest, ref) {
466
100
  return r.status === 0;
467
101
  }
468
102
  /**
469
- * Assemble a workspace skeleton at opts.root: write the canonical root manifest
470
- * (writeWorkspaceManifest) + lay down the root scaffolding (copyWorkspaceTemplate:
471
- * tinycld.packages.ts, vitest.config.ts, tests/ stubs, version files), then clone
472
- * ONLY the tinycld member (merged app shell + core) + the explicitly-requested
473
- * feature members (skipping any already present — e.g. a CI-checked-out member,
474
- * where the merged repo is checked out into the tinycld/ slot). There is NO workspace
475
- * meta-repo clone: bootstrap is the source of all root scaffolding.
103
+ * Assemble a workspace skeleton at opts.root: clone ONLY the tinycld member
104
+ * (merged app shell + core) + the explicitly-requested feature members
105
+ * (skipping any already present — e.g. a CI-checked-out member, where the
106
+ * merged repo is checked out into the tinycld/ slot), then lay down the root
107
+ * scaffolding bootstrap still owns (copyWorkspaceTemplate: tinycld.packages.ts,
108
+ * vitest.config.ts, tests/ stubs, version files) and delegate everything
109
+ * content-bearing about the workspace root (package.json, pnpm-workspace.yaml,
110
+ * package-versions.json, .watchmanconfig, biome.json, .npmrc) to the
111
+ * root-writer script that ships INSIDE the cloned tinycld repo. There is NO
112
+ * workspace meta-repo clone: bootstrap only clones members, never writes
113
+ * vendor version pins or root-file formats itself.
476
114
  * Unknown member names throw. Returns the members that ended up present. Does
477
115
  * NOT run the install (the caller / CI controls that — e.g. `pnpm install` or a
478
116
  * frozen-lockfile install for reproducible builds).
@@ -486,14 +124,6 @@ export function assembleWorkspace(opts) {
486
124
  }
487
125
  const repoBase = opts.repoBase ?? 'git@github.com:tinycld';
488
126
  const clone = opts.clone ?? realClone;
489
- const present = [];
490
- // Write the canonical root manifest (workspaces list + postinstall), then
491
- // lay down the root scaffolding bootstrap owns (tinycld.packages.ts,
492
- // vitest.config.ts, tests/ stubs, .node-version, .go-version). Both are
493
- // non-destructive: anything already present (a real workspace checkout, or
494
- // a CI lane that supplied its own) is left as-is.
495
- writeWorkspaceManifest(opts.root);
496
- copyWorkspaceTemplate(opts.root);
497
127
  // Build the clone set keyed by member NAME so a member passed both bare and
498
128
  // with an @ref dedupes to one clone (the ref-bearing entry wins). The
499
129
  // `tinycld` repo (merged app shell + core, with core + package-scripts
@@ -506,6 +136,7 @@ export function assembleWorkspace(opts) {
506
136
  if (ref !== undefined || !refByName.has(name))
507
137
  refByName.set(name, ref);
508
138
  }
139
+ const present = [];
509
140
  for (const [name, ref] of refByName) {
510
141
  const dest = join(opts.root, name);
511
142
  if (existsSync(join(dest, '.git')) || existsSync(join(dest, 'package.json'))) {
@@ -515,6 +146,14 @@ export function assembleWorkspace(opts) {
515
146
  if (clone(`${repoBase}/${name}.git`, dest, ref))
516
147
  present.push(name);
517
148
  }
149
+ // Root scaffolding AFTER cloning: the static template files bootstrap
150
+ // still owns (never overwrites), then the cloned repo's own root-writer
151
+ // for everything content-bearing. Skip delegation when tinycld itself
152
+ // failed to clone — the caller (runAssembleOnly) raises the clearer
153
+ // "failed to clone required member" error.
154
+ copyWorkspaceTemplate(opts.root);
155
+ if (present.includes('tinycld'))
156
+ delegateWorkspaceRoot(opts.root);
518
157
  return present;
519
158
  }
520
159
  //# sourceMappingURL=assemble-workspace.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assemble-workspace.js","sourceRoot":"","sources":["../src/assemble-workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assemble-workspace.js","sourceRoot":"","sources":["../src/assemble-workspace.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinycld/bootstrap",
3
- "version": "2.4.8",
3
+ "version": "3.1.0",
4
4
  "description": "Scaffolder for @tinycld feature packages",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,13 +24,5 @@
24
24
  },
25
25
  "devDependencies": {
26
26
  "@tinycld/package-scripts": "*"
27
- },
28
- "peerDependencies": {
29
- "react": ">=19",
30
- "react-native": ">=0.83",
31
- "expo-router": ">=55.0.0",
32
- "pbtsdb": ">=0.6.3",
33
- "@tanstack/react-db": ">=0.3",
34
- "@tanstack/db": ">=0.8"
35
27
  }
36
28
  }
@@ -4,7 +4,10 @@ import type { createCollection } from 'pbtsdb/core'
4
4
  import { BasicIndex } from 'pbtsdb/core'
5
5
  import type { {{PKG_PASCAL}}Schema } from './types'
6
6
 
7
- type MergedSchema = Schema & {{PKG_PASCAL}}Schema
7
+ // Replace (not intersect) the generated entries for this package's own
8
+ // collections — a plain intersection would merge each overlapping entry
9
+ // field-wise, letting a generated `any` absorb any typed override.
10
+ type MergedSchema = Omit<Schema, keyof {{PKG_PASCAL}}Schema> & {{PKG_PASCAL}}Schema
8
11
 
9
12
  // Collections contributed by this package. Core calls this during pbtsdb
10
13
  // bootstrap; the returned object's keys become top-level keys on the app's
@@ -13,12 +16,25 @@ export function registerCollections(
13
16
  newCollection: ReturnType<typeof createCollection<MergedSchema>>,
14
17
  _core: CoreStores
15
18
  ) {
19
+ // Hoisted rather than inlined: an inline `collectionOptions` object literal
20
+ // defeats inference of `alwaysFetchRelations` against `relations`, typing every
21
+ // expand path as `never`.
22
+ const indexing = {
23
+ autoIndex: 'eager' as const,
24
+ defaultIndexType: BasicIndex,
25
+ }
26
+
27
+ // `relations` declares where PocketBase's expanded records are filed. Rows
28
+ // never carry `expand`; read a relation from its own collection, with
29
+ // `materialize()` in a select, a join, or `collection.get(id)`.
30
+ // relations: { owner: _core.users },
31
+ // Add `alwaysFetchRelations: ['owner']` only when this collection is the
32
+ // sole path by which rows enter an ON-DEMAND target — an eager target syncs
33
+ // itself, so fetching its relation only adds payload. For one query,
34
+ // use `collection.fetchRelations('owner')` instead.
16
35
  const {{PKG_SNAKE}}_items = newCollection('{{PKG_SNAKE}}_items', {
17
36
  omitOnInsert: ['created', 'updated'] as const,
18
- collectionOptions: {
19
- autoIndex: 'eager' as const,
20
- defaultIndexType: BasicIndex,
21
- },
37
+ collectionOptions: indexing,
22
38
  })
23
39
 
24
40
  return {
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react'
2
2
 
3
- // Optional context provider mounted by core inside /a/[orgSlug]/{{PKG_SLUG}}.
3
+ // Optional context provider mounted by core inside /a/{{PKG_SLUG}}.
4
4
  // Wrap any package-specific context providers or run mount-time side
5
5
  // effects (e.g. subscribing to events, prefetching queries) here.
6
6
  //
@@ -3,11 +3,11 @@ import { useLocalSearchParams } from 'expo-router'
3
3
  import { Text, View } from 'react-native'
4
4
 
5
5
  // Dynamic detail route. Wired automatically by core's generator to
6
- // /a/<orgSlug>/{{PKG_SLUG}}/<id>.
6
+ // /a/{{PKG_SLUG}}/<id>.
7
7
  //
8
- // To navigate back to the index or to another org-scoped screen, use
9
- // `useOrgHref()` from `@tinycld/core/lib/org-routes`. See
10
- // https://tinycld.org/docs/tasks/routing
8
+ // To navigate back to the index or to another app screen, use `useOrgHref()`
9
+ // from `@tinycld/core/lib/org-routes` rather than a literal path — it supplies
10
+ // the /a app-route prefix. See https://tinycld.org/docs/tasks/routing
11
11
 
12
12
  export default function {{PKG_PASCAL}}Detail() {
13
13
  const { id } = useLocalSearchParams<{ id: string }>()
@@ -1,13 +1,16 @@
1
1
  import { useThemeColor } from '@tinycld/core/lib/use-app-theme'
2
2
  import { ScrollView, Text, View } from 'react-native'
3
3
 
4
- // Org-scoped index route for {{PKG_NAME}}, served at /a/<orgSlug>/{{PKG_SLUG}}.
4
+ // Index route for {{PKG_NAME}}, served at /a/{{PKG_SLUG}}.
5
5
  // Replace this placeholder with your list view (cards, table, whatever you
6
- // need) and wire it to your pbtsdb collections using `useOrgLiveQuery`.
6
+ // need) and wire it to your pbtsdb collections using `useLiveQuery`
7
+ // (or `useMyLiveQuery` when the query filters the current user's own rows).
7
8
  //
8
9
  // For navigation, use `useOrgHref()` from `@tinycld/core/lib/org-routes` —
9
- // never literal paths like `router.push('/{{PKG_SLUG}}/new')`, which drop
10
- // the org segment. See https://tinycld.org/docs/tasks/routing
10
+ // never literal paths like `router.push('/{{PKG_SLUG}}/new')`, which miss the
11
+ // /a app-route prefix and resolve to +not-found. `/a` is a constant segment,
12
+ // not an org slug: single-org deployments give each org its own host, so
13
+ // nothing interpolates into it. See https://tinycld.org/docs/tasks/routing
11
14
  //
12
15
  // const orgHref = useOrgHref()
13
16
  // router.push(orgHref('{{PKG_SLUG}}/new'))
@@ -2,7 +2,7 @@ import { useThemeColor } from '@tinycld/core/lib/use-app-theme'
2
2
  import { Text, View } from 'react-native'
3
3
 
4
4
  // Sidebar for the {{PKG_NAME}} package. Rendered in the workspace drawer
5
- // when a user is on any /a/<orgSlug>/{{PKG_SLUG}}/... route.
5
+ // when a user is on any /a/{{PKG_SLUG}}/... route.
6
6
  //
7
7
  // Replace with real navigation (folders, favorites, filters, etc). See
8
8
  // @tinycld/calendar or @tinycld/mail sidebars for richer examples.
@@ -19,10 +19,5 @@
19
19
  },
20
20
  "devDependencies": {
21
21
  "@tinycld/package-scripts": "*"
22
- },
23
- "peerDependencies": {
24
- "react": ">=19",
25
- "react-native": ">=0.83",
26
- "expo-router": ">=55.0.0"
27
22
  }
28
23
  }
@@ -2,7 +2,7 @@ import { useThemeColor } from '@tinycld/core/lib/use-app-theme'
2
2
  import { ScrollView, Text, View } from 'react-native'
3
3
 
4
4
  // The settings panel for {{PKG_NAME}}. Mounted by core inside
5
- // /a/<orgSlug>/settings/{{PKG_SLUG}}/{{PKG_SLUG}} via the manifest's
5
+ // /a/settings/{{PKG_SLUG}}/{{PKG_SLUG}} via the manifest's
6
6
  // `settings[]` entry. This is the only screen the package contributes.
7
7
 
8
8
  export default function {{PKG_PASCAL}}Settings() {
@@ -54,6 +54,36 @@ function memberDirsByName(): Map<string, string> {
54
54
  // not a dir, or no/unreadable package.json — skip
55
55
  }
56
56
  }
57
+ // A member may also be nested one level down inside a dir that is not
58
+ // itself a member — hosting/ui, whose parent ships Go only and stays out of
59
+ // the workspace. Scanned after the top level so a top-level member wins a
60
+ // name collision.
61
+ for (const entry of fs.readdirSync(WS_ROOT)) {
62
+ if (entry === 'node_modules' || entry.startsWith('.')) continue
63
+ const parent = path.join(WS_ROOT, entry)
64
+ let nestedEntries: string[]
65
+ try {
66
+ if (!fs.statSync(parent).isDirectory()) continue
67
+ nestedEntries = fs.readdirSync(parent)
68
+ } catch {
69
+ continue
70
+ }
71
+ for (const nested of nestedEntries) {
72
+ if (nested === 'node_modules' || nested.startsWith('.')) continue
73
+ const dir = path.join(parent, nested)
74
+ try {
75
+ if (!fs.statSync(dir).isDirectory()) continue
76
+ const name = JSON.parse(
77
+ fs.readFileSync(path.join(dir, 'package.json'), 'utf8')
78
+ ).name
79
+ if (typeof name === 'string' && name.length > 0 && !index.has(name)) {
80
+ index.set(name, dir)
81
+ }
82
+ } catch {
83
+ // not a dir, or no/unreadable package.json — skip
84
+ }
85
+ }
86
+ }
57
87
  // @tinycld/core lives nested in the app shell (<WS_ROOT>/<APP_DIR>/core/);
58
88
  // the top-level scan won't find it. Register it explicitly so the
59
89
  // node_modules/@tinycld/core symlink is still created.
@@ -81,13 +111,25 @@ function linkInto(nodeModulesDir: string, scopeName: string, targetDir: string):
81
111
 
82
112
  // If a correct symlink already exists, leave it. Otherwise replace whatever
83
113
  // is there (stale link, or pnpm's own link for a depended-on member).
114
+ //
115
+ // The removal must not sit behind lstat in the same try: a DANGLING symlink
116
+ // (one whose target moved) makes readlink/lstat throw, and the old shape
117
+ // then skipped rmSync and fell straight into symlinkSync -> EEXIST, failing
118
+ // the whole install.
119
+ let matches = false
84
120
  try {
85
- if (fs.lstatSync(linkPath).isSymbolicLink() && fs.readlinkSync(linkPath) === relTarget) {
86
- return
87
- }
88
- fs.rmSync(linkPath, { recursive: true, force: true })
121
+ matches = fs.lstatSync(linkPath).isSymbolicLink() && fs.readlinkSync(linkPath) === relTarget
89
122
  } catch {
90
- // nothing at linkPath yet
123
+ // nothing at linkPath, or a link we cannot read — treat as "replace it"
124
+ }
125
+ if (matches) return
126
+ // unlink first for the symlink case: rmSync follows a link to decide how to
127
+ // remove it, so a DANGLING one (target moved) is left in place and the
128
+ // symlinkSync below then fails EEXIST.
129
+ try {
130
+ fs.unlinkSync(linkPath)
131
+ } catch {
132
+ fs.rmSync(linkPath, { recursive: true, force: true })
91
133
  }
92
134
  fs.symlinkSync(relTarget, linkPath)
93
135
  }
@@ -52,3 +52,52 @@ describe('getPackages (new layout)', () => {
52
52
  expect(getPackages()[0]).toBe('@tinycld/core')
53
53
  })
54
54
  })
55
+
56
+ // A member may live one level down inside a dir that is NOT itself a member:
57
+ // hosting/ui, whose parent ships Go only and is deliberately kept out of the
58
+ // workspace, so its client surface stays in the hosting repo rather than
59
+ // needing one of its own.
60
+ describe('getPackages (nested members)', () => {
61
+ let root: string
62
+ beforeEach(() => {
63
+ root = makeWorkspace()
64
+ process.env.TINYCLD_WS_ROOT = root
65
+ resetPackagesCache()
66
+ })
67
+ afterEach(() => {
68
+ delete process.env.TINYCLD_WS_ROOT
69
+ fs.rmSync(root, { recursive: true, force: true })
70
+ })
71
+
72
+ it('finds a member nested inside a non-member parent', () => {
73
+ const ui = path.join(root, 'hosting', 'ui')
74
+ fs.mkdirSync(ui, { recursive: true })
75
+ fs.writeFileSync(path.join(ui, 'manifest.ts'), 'export default {}')
76
+ fs.writeFileSync(path.join(ui, 'package.json'), JSON.stringify({ name: '@tinycld/host-ui' }))
77
+ // The parent itself is not a member: no manifest.ts of its own.
78
+ fs.writeFileSync(path.join(root, 'hosting', 'go.mod'), 'module example.test\n')
79
+
80
+ resetPackagesCache()
81
+ expect(getPackages()).toContain('@tinycld/host-ui')
82
+ })
83
+
84
+ it('does not descend into a dir that is itself a member', () => {
85
+ // A feature's own subdirectory is its source tree, not another member.
86
+ const inner = path.join(root, 'contacts', 'vendored')
87
+ fs.mkdirSync(inner, { recursive: true })
88
+ fs.writeFileSync(path.join(inner, 'manifest.ts'), 'export default {}')
89
+ fs.writeFileSync(
90
+ path.join(inner, 'package.json'),
91
+ JSON.stringify({ name: '@tinycld/should-not-appear' })
92
+ )
93
+
94
+ resetPackagesCache()
95
+ expect(getPackages()).not.toContain('@tinycld/should-not-appear')
96
+ })
97
+
98
+ it('lists each member once when a nested scan revisits the tree', () => {
99
+ resetPackagesCache()
100
+ const pkgs = getPackages()
101
+ expect(new Set(pkgs).size).toBe(pkgs.length)
102
+ })
103
+ })
@@ -49,6 +49,25 @@ export function getPackages(): string[] {
49
49
  if (hasManifest(dir)) features.push(name)
50
50
  }
51
51
 
52
+ // A member may also be nested one level down, inside a dir that is not
53
+ // itself a member — hosting/ui is the case: hosting/ ships Go only and
54
+ // stays out of the workspace, but its client surface has to be a
55
+ // package the generator sees, and keeping it there keeps it in the
56
+ // hosting repo. One level only: deep enough for this shape, shallow
57
+ // enough not to walk the tree.
58
+ for (const entry of fs.readdirSync(wsRoot)) {
59
+ const parent = path.join(wsRoot, entry)
60
+ if (entry === 'node_modules' || entry.startsWith('.')) continue
61
+ if (!isDir(parent) || hasManifest(parent)) continue
62
+ for (const nested of readdirSafe(parent)) {
63
+ const dir = path.join(parent, nested)
64
+ if (nested === 'node_modules' || nested.startsWith('.')) continue
65
+ if (!isDir(dir) || !hasManifest(dir)) continue
66
+ const name = readPackageName(dir)
67
+ if (name && name !== CORE_NAME) features.push(name)
68
+ }
69
+ }
70
+
52
71
  // @tinycld/core moved INSIDE the app shell (at <appDir>/core/); the
53
72
  // top-level scan above won't see it. Look for it explicitly so it is
54
73
  // still listed first. The app dir is normally <wsRoot>/tinycld, but EAS
@@ -78,6 +97,23 @@ export function resetPackagesCache(): void {
78
97
  cached = null
79
98
  }
80
99
 
100
+ function readdirSafe(dir: string): string[] {
101
+ try {
102
+ return fs.readdirSync(dir)
103
+ } catch {
104
+ return []
105
+ }
106
+ }
107
+
108
+ function readPackageName(dir: string): string | null {
109
+ try {
110
+ const name = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).name
111
+ return typeof name === 'string' && name.length > 0 ? name : null
112
+ } catch {
113
+ return null
114
+ }
115
+ }
116
+
81
117
  function hasManifest(dir: string): boolean {
82
118
  return (
83
119
  fs.existsSync(path.join(dir, 'manifest.ts')) || fs.existsSync(path.join(dir, 'manifest.js'))