@tinycld/bootstrap 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap-tooling.js +139 -30
- package/dist/bootstrap-tooling.js.map +1 -1
- package/dist/index.js +55 -21
- package/dist/index.js.map +1 -1
- package/dist/layout.js +7 -20
- package/dist/layout.js.map +1 -1
- package/dist/link-package.js +78 -62
- package/dist/link-package.js.map +1 -1
- package/dist/prompts.js +3 -3
- package/dist/prompts.js.map +1 -1
- package/package.json +1 -1
- package/templates/full/package.json +7 -1
- package/templates/full/playwright.config.ts +20 -0
- package/templates/full/server/go.mod +2 -2
- package/templates/settings-only/package.json +6 -1
- package/templates/shared/.github/workflows/ci.yml +62 -38
- package/templates/shared/README.md +40 -29
- package/templates/shared/tsconfig.json +5 -14
- package/templates/shared/vitest.config.ts +16 -0
- package/templates/workspace/.go-version +1 -0
- package/templates/workspace/.node-version +1 -0
- package/templates/workspace/tests/expo-clipboard-stub.ts +20 -0
- package/templates/workspace/tests/expo-router-stub.ts +48 -0
- package/templates/workspace/tests/lucide-react-native-stub.cjs +73 -0
- package/templates/workspace/tests/tinycld.packages.test.ts +54 -0
- package/templates/workspace/tests/unit-setup.ts +55 -0
- package/templates/workspace/tinycld.packages.ts +75 -0
- package/templates/workspace/vitest.config.ts +104 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
process.env.EXPO_PUBLIC_ENV ??= 'test'
|
|
4
|
+
|
|
5
|
+
// tinycld.config.ts is the runtime source of truth, but loading the real one
|
|
6
|
+
// pulls every linked package's collections/screens (and their un-mockable RN
|
|
7
|
+
// surface) into the test graph. Shim it to an empty config so unit tests stay
|
|
8
|
+
// light; tests that need specific packages can override per-file.
|
|
9
|
+
vi.mock('@tinycld/app-generated/tinycld-config', () => ({
|
|
10
|
+
tinycldConfig: [],
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock('@sentry/react-native', () => ({
|
|
14
|
+
init: vi.fn(),
|
|
15
|
+
captureException: vi.fn(),
|
|
16
|
+
withScope: vi.fn(),
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
// React Native's entry uses Flow syntax Vite/Rollup can't parse. Substitute
|
|
20
|
+
// the minimal surface our tests touch.
|
|
21
|
+
vi.mock('react-native', () => ({
|
|
22
|
+
Platform: { OS: 'web' },
|
|
23
|
+
Dimensions: {
|
|
24
|
+
get: () => ({ width: 1024, height: 768 }),
|
|
25
|
+
addEventListener: () => ({ remove: () => {} }),
|
|
26
|
+
},
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
// @react-native-async-storage/async-storage requires the RN module bridge and
|
|
30
|
+
// is pulled in transitively by `~/lib/store`. Substitute a minimal in-memory
|
|
31
|
+
// shim so tests can exercise the Zustand registry without RN.
|
|
32
|
+
vi.mock('@react-native-async-storage/async-storage', () => {
|
|
33
|
+
const store = new Map<string, string>()
|
|
34
|
+
const api = {
|
|
35
|
+
getItem: async (key: string) => store.get(key) ?? null,
|
|
36
|
+
setItem: async (key: string, value: string) => {
|
|
37
|
+
store.set(key, value)
|
|
38
|
+
},
|
|
39
|
+
removeItem: async (key: string) => {
|
|
40
|
+
store.delete(key)
|
|
41
|
+
},
|
|
42
|
+
clear: async () => {
|
|
43
|
+
store.clear()
|
|
44
|
+
},
|
|
45
|
+
getAllKeys: async () => Array.from(store.keys()),
|
|
46
|
+
multiGet: async (keys: string[]) => keys.map(k => [k, store.get(k) ?? null]),
|
|
47
|
+
multiSet: async (pairs: [string, string][]) => {
|
|
48
|
+
for (const [k, v] of pairs) store.set(k, v)
|
|
49
|
+
},
|
|
50
|
+
multiRemove: async (keys: string[]) => {
|
|
51
|
+
for (const k of keys) store.delete(k)
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
return { default: api, ...api }
|
|
55
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
|
|
4
|
+
// New flat layout: every package (core, app, features) is a direct member dir
|
|
5
|
+
// under the workspace root. core is the member whose package.json name is
|
|
6
|
+
// "@tinycld/core" (it has no manifest.ts). Feature members are dirs with a
|
|
7
|
+
// manifest.ts. Members without a manifest.ts are excluded (this includes the app shell).
|
|
8
|
+
//
|
|
9
|
+
// TINYCLD_WS_ROOT overrides the workspace root for tests that scan a fake tree.
|
|
10
|
+
// It is resolved at call time (not module load) so that tests can set it in
|
|
11
|
+
// beforeEach after the module is already imported.
|
|
12
|
+
const CORE_NAME = '@tinycld/core'
|
|
13
|
+
|
|
14
|
+
let cached: string[] | null = null
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns the package names linked into the app, core first then features
|
|
18
|
+
* sorted by name. Core is the member named "@tinycld/core" (no manifest.ts);
|
|
19
|
+
* feature members are dirs containing a manifest.ts with a named package.json.
|
|
20
|
+
* The app shell is excluded. Cached per process; reset with resetPackagesCache().
|
|
21
|
+
*/
|
|
22
|
+
export function getPackages(): string[] {
|
|
23
|
+
if (cached) return cached
|
|
24
|
+
|
|
25
|
+
const wsRoot = process.env.TINYCLD_WS_ROOT
|
|
26
|
+
? path.resolve(process.env.TINYCLD_WS_ROOT)
|
|
27
|
+
: path.resolve(import.meta.dirname)
|
|
28
|
+
|
|
29
|
+
let core: string | null = null
|
|
30
|
+
const features: string[] = []
|
|
31
|
+
|
|
32
|
+
if (fs.existsSync(wsRoot)) {
|
|
33
|
+
for (const entry of fs.readdirSync(wsRoot)) {
|
|
34
|
+
const dir = path.join(wsRoot, entry)
|
|
35
|
+
if (!isDir(dir)) continue
|
|
36
|
+
const pkgJsonPath = path.join(dir, 'package.json')
|
|
37
|
+
if (!fs.existsSync(pkgJsonPath)) continue
|
|
38
|
+
let name: unknown
|
|
39
|
+
try {
|
|
40
|
+
name = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')).name
|
|
41
|
+
} catch {
|
|
42
|
+
continue // unreadable package.json
|
|
43
|
+
}
|
|
44
|
+
if (typeof name !== 'string' || name.length === 0) continue
|
|
45
|
+
if (name === CORE_NAME) {
|
|
46
|
+
core = name
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
if (hasManifest(dir)) features.push(name)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
features.sort()
|
|
54
|
+
cached = core ? [core, ...features] : features
|
|
55
|
+
return cached
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Test-only: clear the per-process cache so a fresh TINYCLD_WS_ROOT is read.
|
|
59
|
+
export function resetPackagesCache(): void {
|
|
60
|
+
cached = null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasManifest(dir: string): boolean {
|
|
64
|
+
return (
|
|
65
|
+
fs.existsSync(path.join(dir, 'manifest.ts')) || fs.existsSync(path.join(dir, 'manifest.js'))
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isDir(p: string): boolean {
|
|
70
|
+
try {
|
|
71
|
+
return fs.statSync(p).isDirectory()
|
|
72
|
+
} catch {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { defineConfig } from 'vitest/config'
|
|
4
|
+
|
|
5
|
+
// Workspace-root vitest config. Supplies dedup pins, stub aliases, and the
|
|
6
|
+
// react-native/expo/sentry mocks that let the broader core suite run under
|
|
7
|
+
// Node without the Metro/RN runtime.
|
|
8
|
+
const ROOT = import.meta.dirname
|
|
9
|
+
const CORE_DIR = path.join(ROOT, 'core')
|
|
10
|
+
const APP_GENERATED = path.join(ROOT, 'app', 'lib', 'generated')
|
|
11
|
+
|
|
12
|
+
// Locate a hoisted dependency's dir. In this workspace layout deps hoist to
|
|
13
|
+
// the workspace-root node_modules (all members declare framework deps as
|
|
14
|
+
// peerDependencies only, so the app shell's direct deps live in
|
|
15
|
+
// node_modules/ here at the root). Falls back one level up (CI layouts where
|
|
16
|
+
// the workspace root is nested under another root).
|
|
17
|
+
const pkgDir = (pkg: string): string => {
|
|
18
|
+
for (const root of [path.join(ROOT, 'node_modules'), path.join(ROOT, '..', 'node_modules')]) {
|
|
19
|
+
const candidate = path.join(root, pkg)
|
|
20
|
+
if (fs.existsSync(candidate)) return candidate
|
|
21
|
+
}
|
|
22
|
+
return path.join(ROOT, 'node_modules', pkg)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
resolve: {
|
|
27
|
+
alias: [
|
|
28
|
+
// --- dedup pins (Vite SSR resolver differs from Metro; keep until
|
|
29
|
+
// a test proves the workspace dedupes these on its own) ---
|
|
30
|
+
{ find: /^react$/, replacement: path.join(pkgDir('react'), 'index.js') },
|
|
31
|
+
{
|
|
32
|
+
find: /^react\/jsx-runtime$/,
|
|
33
|
+
replacement: path.join(pkgDir('react'), 'jsx-runtime.js'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
find: /^react\/jsx-dev-runtime$/,
|
|
37
|
+
replacement: path.join(pkgDir('react'), 'jsx-dev-runtime.js'),
|
|
38
|
+
},
|
|
39
|
+
// yjs/y-protocols use instanceof checks; a duplicate copy reached
|
|
40
|
+
// through a member symlink breaks nested Y.Map.set. Pin to the
|
|
41
|
+
// single install.
|
|
42
|
+
{ find: /^yjs$/, replacement: pkgDir('yjs') },
|
|
43
|
+
{ find: /^y-protocols\/(.+)$/, replacement: `${pkgDir('y-protocols')}/$1` },
|
|
44
|
+
// hyperformula's ESM build has broken relative imports under Vite
|
|
45
|
+
// SSR; pin to the self-consistent commonjs entry.
|
|
46
|
+
{
|
|
47
|
+
find: /^hyperformula$/,
|
|
48
|
+
replacement: path.join(pkgDir('hyperformula'), 'commonjs/index.js'),
|
|
49
|
+
},
|
|
50
|
+
// --- @tinycld/core path remaps. Unlike Metro, Vite's exports-map
|
|
51
|
+
// resolution does NOT do directory-index fallback, so
|
|
52
|
+
// `@tinycld/core/lib/notify` (a dir → lib/notify/index.ts) fails
|
|
53
|
+
// to load through the exports wildcard. Remap straight to the
|
|
54
|
+
// core source path, which Vite resolves with its own extension
|
|
55
|
+
// + index probing.
|
|
56
|
+
{
|
|
57
|
+
find: /^@tinycld\/core$/,
|
|
58
|
+
replacement: path.join(CORE_DIR, 'index.ts'),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
find: /^@tinycld\/core\/(.+)$/,
|
|
62
|
+
replacement: `${CORE_DIR}/$1`,
|
|
63
|
+
},
|
|
64
|
+
// --- @tinycld/app-generated/* build-time contract (written to
|
|
65
|
+
// app/lib/generated/ by the generator; Vitest doesn't read
|
|
66
|
+
// tsconfig paths) ---
|
|
67
|
+
{
|
|
68
|
+
find: /^@tinycld\/app-generated\/(.+)$/,
|
|
69
|
+
replacement: `${APP_GENERATED}/$1`,
|
|
70
|
+
},
|
|
71
|
+
// --- test doubles ---
|
|
72
|
+
// expo-clipboard transitively pulls in expo-modules-core, whose
|
|
73
|
+
// module-load-time side effects don't survive a bare Node test env.
|
|
74
|
+
{
|
|
75
|
+
find: /^expo-clipboard$/,
|
|
76
|
+
replacement: path.join(ROOT, 'tests/expo-clipboard-stub.ts'),
|
|
77
|
+
},
|
|
78
|
+
// expo-router's CJS entry does `require("./global")` at module top,
|
|
79
|
+
// which Node can't resolve when reached through a member symlink.
|
|
80
|
+
{
|
|
81
|
+
find: /^expo-router$/,
|
|
82
|
+
replacement: path.join(ROOT, 'tests/expo-router-stub.ts'),
|
|
83
|
+
},
|
|
84
|
+
// lucide-react-native pulls in react-native-svg (TS source, not
|
|
85
|
+
// transformed in node_modules under Vitest); a CJS Proxy stub
|
|
86
|
+
// yields a harmless component for any named icon import.
|
|
87
|
+
{
|
|
88
|
+
find: /^lucide-react-native$/,
|
|
89
|
+
replacement: path.join(ROOT, 'tests/lucide-react-native-stub.cjs'),
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
test: {
|
|
94
|
+
environment: 'node',
|
|
95
|
+
include: [
|
|
96
|
+
'tests/**/*.test.{ts,tsx}',
|
|
97
|
+
'core/**/__tests__/**/*.test.{ts,tsx}',
|
|
98
|
+
'core/**/*.test.{ts,tsx}',
|
|
99
|
+
'app/scripts/**/__tests__/**/*.test.{ts,tsx}',
|
|
100
|
+
'package-scripts/tests/**/*.test.{ts,tsx}',
|
|
101
|
+
],
|
|
102
|
+
setupFiles: ['tests/unit-setup.ts'],
|
|
103
|
+
},
|
|
104
|
+
})
|