@tinycld/bootstrap 1.0.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.
Files changed (43) hide show
  1. package/README.md +297 -0
  2. package/dist/args.js +130 -0
  3. package/dist/args.js.map +1 -0
  4. package/dist/bootstrap-tooling.js +66 -0
  5. package/dist/bootstrap-tooling.js.map +1 -0
  6. package/dist/copy-template.js +96 -0
  7. package/dist/copy-template.js.map +1 -0
  8. package/dist/index.js +134 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/layout.js +40 -0
  11. package/dist/layout.js.map +1 -0
  12. package/dist/link-core.js +99 -0
  13. package/dist/link-core.js.map +1 -0
  14. package/dist/link-package.js +85 -0
  15. package/dist/link-package.js.map +1 -0
  16. package/dist/prompts.js +187 -0
  17. package/dist/prompts.js.map +1 -0
  18. package/dist/substitute.js +35 -0
  19. package/dist/substitute.js.map +1 -0
  20. package/dist/validate.js +65 -0
  21. package/dist/validate.js.map +1 -0
  22. package/package.json +46 -0
  23. package/templates/full/manifest.ts +21 -0
  24. package/templates/full/package.json +30 -0
  25. package/templates/full/pb-migrations/1800000000_create_{{PKG_SLUG}}.js +67 -0
  26. package/templates/full/server/go.mod +10 -0
  27. package/templates/full/server/register.go +20 -0
  28. package/templates/full/tinycld/{{PKG_SLUG}}/collections.ts +27 -0
  29. package/templates/full/tinycld/{{PKG_SLUG}}/provider.tsx +16 -0
  30. package/templates/full/tinycld/{{PKG_SLUG}}/screens/[id].tsx +23 -0
  31. package/templates/full/tinycld/{{PKG_SLUG}}/screens/_layout.tsx +5 -0
  32. package/templates/full/tinycld/{{PKG_SLUG}}/screens/index.tsx +28 -0
  33. package/templates/full/tinycld/{{PKG_SLUG}}/seed.ts +22 -0
  34. package/templates/full/tinycld/{{PKG_SLUG}}/sidebar.tsx +20 -0
  35. package/templates/full/tinycld/{{PKG_SLUG}}/types.ts +18 -0
  36. package/templates/settings-only/manifest.ts +15 -0
  37. package/templates/settings-only/package.json +23 -0
  38. package/templates/settings-only/tinycld/{{PKG_SLUG}}/settings/main.tsx +20 -0
  39. package/templates/settings-only/tinycld/{{PKG_SLUG}}/types.ts +5 -0
  40. package/templates/shared/.github/workflows/ci.yml +59 -0
  41. package/templates/shared/README.md +54 -0
  42. package/templates/shared/tests/manifest.test.ts +14 -0
  43. package/templates/shared/tsconfig.json +22 -0
@@ -0,0 +1,65 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ const SLUG_RE = /^[a-z][a-z0-9-]*[a-z0-9]$/;
3
+ const RESERVED_SLUGS = new Set(['core', 'main', 'test', 'tests', 'node_modules', 'packages']);
4
+ const SHORTCUT_RE = /^[a-z]$/;
5
+ const ICON_RE = /^[a-z][a-z0-9-]*$/;
6
+ export function validateSlug(slug) {
7
+ if (!slug || slug.length < 3)
8
+ return 'Slug must be at least 3 characters';
9
+ if (slug.length > 40)
10
+ return 'Slug must be 40 characters or fewer';
11
+ if (!SLUG_RE.test(slug)) {
12
+ return 'Slug must be kebab-case (lowercase letters, digits, hyphens; no leading/trailing hyphen)';
13
+ }
14
+ if (RESERVED_SLUGS.has(slug))
15
+ return `"${slug}" is reserved`;
16
+ return null;
17
+ }
18
+ export function validateName(name) {
19
+ if (!name || name.trim().length === 0)
20
+ return 'Name is required';
21
+ if (name.length > 60)
22
+ return 'Name must be 60 characters or fewer';
23
+ return null;
24
+ }
25
+ export function validateDescription(desc) {
26
+ if (!desc || desc.trim().length === 0)
27
+ return 'Description is required';
28
+ if (desc.length > 200)
29
+ return 'Description must be 200 characters or fewer';
30
+ return null;
31
+ }
32
+ export function validateIcon(icon) {
33
+ if (!icon || icon.length === 0)
34
+ return null; // optional
35
+ if (!ICON_RE.test(icon))
36
+ return 'Icon must be a lucide-react-native name (lowercase, hyphens)';
37
+ return null;
38
+ }
39
+ export function validateNavOrder(raw) {
40
+ const n = Number(raw);
41
+ if (!Number.isInteger(n))
42
+ return 'Nav order must be an integer';
43
+ if (n < 0 || n > 99)
44
+ return 'Nav order must be between 0 and 99';
45
+ return null;
46
+ }
47
+ export function validateShortcut(s) {
48
+ if (!s || s.length === 0)
49
+ return null; // optional
50
+ if (!SHORTCUT_RE.test(s))
51
+ return 'Shortcut must be a single lowercase letter';
52
+ return null;
53
+ }
54
+ export function validateTargetDir(dir) {
55
+ if (!existsSync(dir))
56
+ return null;
57
+ const stat = statSync(dir);
58
+ if (!stat.isDirectory())
59
+ return `${dir} exists and is not a directory`;
60
+ const entries = readdirSync(dir).filter((n) => n !== '.DS_Store');
61
+ if (entries.length > 0)
62
+ return `${dir} exists and is not empty`;
63
+ return null;
64
+ }
65
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@tinycld/bootstrap",
3
+ "version": "1.0.0",
4
+ "description": "Scaffolder for @tinycld feature packages",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tinycld/bootstrap.git"
10
+ },
11
+ "bin": {
12
+ "bootstrap": "dist/index.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "templates"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsx src/index.ts",
24
+ "lint": "biome check .",
25
+ "lint:fix": "biome check --write .",
26
+ "typecheck": "tsc --noEmit",
27
+ "checks": "npm run lint && npm run typecheck",
28
+ "test": "vitest run",
29
+ "prepublishOnly": "npm run checks && npm run test && npm run build",
30
+ "release": "npm publish"
31
+ },
32
+ "dependencies": {
33
+ "@clack/prompts": "^0.8.0",
34
+ "picocolors": "^1.1.0"
35
+ },
36
+ "devDependencies": {
37
+ "@biomejs/biome": "^2.4.12",
38
+ "@types/node": "^22.10.0",
39
+ "tsx": "^4.21.0",
40
+ "typescript": "^5.9.0",
41
+ "vitest": "^3.0.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=24"
45
+ }
46
+ }
@@ -0,0 +1,21 @@
1
+ const manifest = {
2
+ name: '{{PKG_NAME}}',
3
+ slug: '{{PKG_SLUG}}',
4
+ version: '0.1.0',
5
+ description: '{{PKG_DESCRIPTION}}',
6
+ routes: { directory: 'screens' },
7
+ nav: {
8
+ label: '{{PKG_NAME}}',
9
+ icon: '{{PKG_ICON}}',
10
+ order: {{PKG_NAV_ORDER}},
11
+ shortcut: '{{PKG_NAV_SHORTCUT}}',
12
+ },
13
+ sidebar: { component: 'sidebar' },
14
+ provider: { component: 'provider' },
15
+ migrations: { directory: 'pb-migrations' },
16
+ collections: { register: 'collections', types: 'types' },
17
+ seed: { script: 'seed' },
18
+ server: { package: 'server', module: '{{GO_MODULE}}' },
19
+ }
20
+
21
+ export default manifest
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@tinycld/{{PKG_SLUG}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "{{PKG_DESCRIPTION}}",
7
+ "exports": {
8
+ "./package.json": "./package.json",
9
+ "./manifest": "./manifest.ts",
10
+ "./types": "./tinycld/{{PKG_SLUG}}/types.ts",
11
+ "./collections": "./tinycld/{{PKG_SLUG}}/collections.ts",
12
+ "./screens/*": "./tinycld/{{PKG_SLUG}}/screens/*.tsx",
13
+ "./seed": "./tinycld/{{PKG_SLUG}}/seed.ts",
14
+ "./sidebar": "./tinycld/{{PKG_SLUG}}/sidebar.tsx",
15
+ "./provider": "./tinycld/{{PKG_SLUG}}/provider.tsx",
16
+ "./hooks/*": "./tinycld/{{PKG_SLUG}}/hooks/*.ts",
17
+ "./components/*": "./tinycld/{{PKG_SLUG}}/components/*.tsx"
18
+ },
19
+ "scripts": {
20
+ "typecheck": "tsc --noEmit --skipLibCheck"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=19",
24
+ "react-native": ">=0.83",
25
+ "expo-router": ">=55.0.0",
26
+ "pbtsdb": ">=0.5",
27
+ "@tanstack/react-db": ">=0.1",
28
+ "@tanstack/db": ">=0.6"
29
+ }
30
+ }
@@ -0,0 +1,67 @@
1
+ /// <reference path="../pb_data/types.d.ts" />
2
+
3
+ // Initial migration for {{PKG_SLUG}}. Creates a single `{{PKG_SNAKE}}_items`
4
+ // collection matching the shape declared in types.ts / collections.ts.
5
+ // Replace or extend this as you build out the package.
6
+ //
7
+ // Auth rules: every TinyCld collection ships with rules. Without them
8
+ // PocketBase falls back to "superusers only" and every insert/select fails
9
+ // with "Only superusers can perform this action." The rules below assume an
10
+ // org-scoped data model — each row has an `owner` relation pointing at a
11
+ // user_org row, and the rule allows access when the calling user owns that
12
+ // user_org. This matches the pattern used by @tinycld/contacts and friends.
13
+ //
14
+ // If your data isn't org-scoped (user-scoped, public, anything else), pick
15
+ // the right rule pattern from the docs:
16
+ // https://tinycld.org/docs/tasks/auth-rules
17
+
18
+ migrate(
19
+ app => {
20
+ const collection = new Collection({
21
+ type: 'base',
22
+ name: '{{PKG_SNAKE}}_items',
23
+ listRule: 'owner.user = @request.auth.id',
24
+ viewRule: 'owner.user = @request.auth.id',
25
+ createRule: 'owner.user = @request.auth.id',
26
+ updateRule: 'owner.user = @request.auth.id',
27
+ deleteRule: 'owner.user = @request.auth.id',
28
+ fields: [
29
+ {
30
+ name: 'name',
31
+ type: 'text',
32
+ required: true,
33
+ min: 1,
34
+ max: 200,
35
+ },
36
+ {
37
+ name: 'owner',
38
+ type: 'relation',
39
+ required: true,
40
+ collectionId: 'pbc_user_org_01',
41
+ cascadeDelete: true,
42
+ maxSelect: 1,
43
+ },
44
+ {
45
+ name: 'created',
46
+ type: 'autodate',
47
+ onCreate: true,
48
+ onUpdate: false,
49
+ },
50
+ {
51
+ name: 'updated',
52
+ type: 'autodate',
53
+ onCreate: true,
54
+ onUpdate: true,
55
+ },
56
+ ],
57
+ indexes: [
58
+ 'CREATE INDEX `idx_{{PKG_SNAKE}}_items_owner` ON `{{PKG_SNAKE}}_items` (`owner`)',
59
+ ],
60
+ })
61
+ app.save(collection)
62
+ },
63
+ app => {
64
+ const collection = app.findCollectionByNameOrId('{{PKG_SNAKE}}_items')
65
+ app.delete(collection)
66
+ }
67
+ )
@@ -0,0 +1,10 @@
1
+ module {{GO_MODULE}}
2
+
3
+ go 1.25.0
4
+
5
+ require (
6
+ github.com/pocketbase/pocketbase v0.36.8
7
+ tinycld.org/core v0.0.0
8
+ )
9
+
10
+ replace tinycld.org/core => ../../tinycld/packages/@tinycld/core/server
@@ -0,0 +1,20 @@
1
+ package {{PKG_CAMEL}}
2
+
3
+ import (
4
+ "github.com/pocketbase/pocketbase"
5
+ )
6
+
7
+ // Register wires server-side hooks for the {{PKG_NAME}} package. Core's
8
+ // generator injects a call to this function from `server/package_extensions.go`
9
+ // once the package is linked.
10
+ //
11
+ // Typical responsibilities:
12
+ // - Register audit hooks for your collections (see tinycld.org/core/audit).
13
+ // - Bind record lifecycle hooks via `app.OnRecordCreate("...").BindFunc(...)`.
14
+ // - Register HTTP endpoints via `app.OnServe().BindFunc(...)`.
15
+ //
16
+ // See contacts/server/register.go or calendar/server/register.go for richer
17
+ // examples.
18
+ func Register(_ *pocketbase.PocketBase) {
19
+ // No server-side logic yet.
20
+ }
@@ -0,0 +1,27 @@
1
+ import type { CoreStores } from '@tinycld/core/lib/pocketbase'
2
+ import type { Schema } from '@tinycld/core/types/pbSchema'
3
+ import type { createCollection } from 'pbtsdb/core'
4
+ import { BasicIndex } from 'pbtsdb/core'
5
+ import type { {{PKG_PASCAL}}Schema } from './types'
6
+
7
+ type MergedSchema = Schema & {{PKG_PASCAL}}Schema
8
+
9
+ // Collections contributed by this package. Core calls this during pbtsdb
10
+ // bootstrap; the returned object's keys become top-level keys on the app's
11
+ // MergedSchema (accessible via `useStore('...')`).
12
+ export function registerCollections(
13
+ newCollection: ReturnType<typeof createCollection<MergedSchema>>,
14
+ _core: CoreStores
15
+ ) {
16
+ const {{PKG_SNAKE}}_items = newCollection('{{PKG_SNAKE}}_items', {
17
+ omitOnInsert: ['created', 'updated'] as const,
18
+ collectionOptions: {
19
+ autoIndex: 'eager' as const,
20
+ defaultIndexType: BasicIndex,
21
+ },
22
+ })
23
+
24
+ return {
25
+ {{PKG_SNAKE}}_items,
26
+ }
27
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ // Optional context provider mounted by core inside /a/[orgSlug]/{{PKG_SLUG}}.
4
+ // Wrap any package-specific context providers or run mount-time side
5
+ // effects (e.g. subscribing to events, prefetching queries) here.
6
+ //
7
+ // If you don't need a provider, delete this file and remove the
8
+ // `provider` field from manifest.ts.
9
+
10
+ interface Props {
11
+ children: ReactNode
12
+ }
13
+
14
+ export default function {{PKG_PASCAL}}Provider({ children }: Props) {
15
+ return <>{children}</>
16
+ }
@@ -0,0 +1,23 @@
1
+ import { useThemeColor } from '@tinycld/core/lib/use-app-theme'
2
+ import { useLocalSearchParams } from 'expo-router'
3
+ import { Text, View } from 'react-native'
4
+
5
+ // Dynamic detail route. Wired automatically by core's generator to
6
+ // /a/<orgSlug>/{{PKG_SLUG}}/<id>.
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
11
+
12
+ export default function {{PKG_PASCAL}}Detail() {
13
+ const { id } = useLocalSearchParams<{ id: string }>()
14
+ const fg = useThemeColor('foreground')
15
+ const muted = useThemeColor('muted-foreground')
16
+
17
+ return (
18
+ <View className="p-6 gap-3">
19
+ <Text style={{ color: fg, fontSize: 22, fontWeight: '600' }}>{{PKG_NAME}} detail</Text>
20
+ <Text style={{ color: muted, fontSize: 14 }}>Showing item {id}</Text>
21
+ </View>
22
+ )
23
+ }
@@ -0,0 +1,5 @@
1
+ import { Stack } from 'expo-router'
2
+
3
+ export default function {{PKG_PASCAL}}Layout() {
4
+ return <Stack screenOptions={{ headerShown: false }} />
5
+ }
@@ -0,0 +1,28 @@
1
+ import { useThemeColor } from '@tinycld/core/lib/use-app-theme'
2
+ import { ScrollView, Text, View } from 'react-native'
3
+
4
+ // Org-scoped index route for {{PKG_NAME}}, served at /a/<orgSlug>/{{PKG_SLUG}}.
5
+ // Replace this placeholder with your list view (cards, table, whatever you
6
+ // need) and wire it to your pbtsdb collections using `useOrgLiveQuery`.
7
+ //
8
+ // 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
11
+ //
12
+ // const orgHref = useOrgHref()
13
+ // router.push(orgHref('{{PKG_SLUG}}/new'))
14
+ // router.push(orgHref('{{PKG_SLUG}}/[id]', { id: itemId }))
15
+
16
+ export default function {{PKG_PASCAL}}Index() {
17
+ const fg = useThemeColor('foreground')
18
+ const muted = useThemeColor('muted-foreground')
19
+
20
+ return (
21
+ <ScrollView className="flex-1 bg-background">
22
+ <View className="p-6 gap-3">
23
+ <Text style={{ color: fg, fontSize: 22, fontWeight: '600' }}>{{PKG_NAME}}</Text>
24
+ <Text style={{ color: muted, fontSize: 14 }}>Placeholder landing screen for {{PKG_SLUG}}.</Text>
25
+ </View>
26
+ </ScrollView>
27
+ )
28
+ }
@@ -0,0 +1,22 @@
1
+ import type PocketBase from 'pocketbase'
2
+
3
+ // Seed function invoked by core/scripts/seed-db.ts for the primary test org.
4
+ // Receives the authenticated `pb` client plus the user/org/user_org context
5
+ // so records can be owned correctly. Return nothing; throw to abort the seed.
6
+ //
7
+ // Example:
8
+ //
9
+ // await pb.collection('{{PKG_SNAKE}}_items').create({
10
+ // name: 'Sample',
11
+ // owner: userOrg.id,
12
+ // })
13
+
14
+ interface SeedContext {
15
+ user: { id: string; email: string; name: string }
16
+ org: { id: string }
17
+ userOrg: { id: string }
18
+ }
19
+
20
+ export default async function seed(_pb: PocketBase, _ctx: SeedContext): Promise<void> {
21
+ // Add your sample data here.
22
+ }
@@ -0,0 +1,20 @@
1
+ import { useThemeColor } from '@tinycld/core/lib/use-app-theme'
2
+ import { Text, View } from 'react-native'
3
+
4
+ // Sidebar for the {{PKG_NAME}} package. Rendered in the workspace drawer
5
+ // when a user is on any /a/<orgSlug>/{{PKG_SLUG}}/... route.
6
+ //
7
+ // Replace with real navigation (folders, favorites, filters, etc). See
8
+ // @tinycld/calendar or @tinycld/mail sidebars for richer examples.
9
+
10
+ export default function {{PKG_PASCAL}}Sidebar() {
11
+ const fg = useThemeColor('foreground')
12
+ const muted = useThemeColor('muted-foreground')
13
+
14
+ return (
15
+ <View className="p-3 gap-2">
16
+ <Text style={{ color: fg, fontSize: 14, fontWeight: '600' }}>{{PKG_NAME}}</Text>
17
+ <Text style={{ color: muted, fontSize: 12 }}>Replace this with your package's sidebar nav.</Text>
18
+ </View>
19
+ )
20
+ }
@@ -0,0 +1,18 @@
1
+ // Schema types for this package, merged into core's MergedSchema by the
2
+ // generator. Each entry maps a pbtsdb collection name to its record type
3
+ // and optional relations. Rename, replace, or delete {{PKG_SNAKE}}_items
4
+ // when you wire up your first real collection.
5
+
6
+ export interface {{PKG_PASCAL}}Item {
7
+ id: string
8
+ name: string
9
+ owner: string
10
+ created: string
11
+ updated: string
12
+ }
13
+
14
+ export type {{PKG_PASCAL}}Schema = {
15
+ {{PKG_SNAKE}}_items: {
16
+ type: {{PKG_PASCAL}}Item
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ const manifest = {
2
+ name: '{{PKG_NAME}}',
3
+ slug: '{{PKG_SLUG}}',
4
+ version: '0.1.0',
5
+ description: '{{PKG_DESCRIPTION}}',
6
+ settings: [
7
+ {
8
+ slug: '{{PKG_SLUG}}',
9
+ component: 'settings/main',
10
+ label: '{{PKG_NAME}}',
11
+ },
12
+ ],
13
+ }
14
+
15
+ export default manifest
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@tinycld/{{PKG_SLUG}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "{{PKG_DESCRIPTION}}",
7
+ "exports": {
8
+ "./package.json": "./package.json",
9
+ "./manifest": "./manifest.ts",
10
+ "./types": "./tinycld/{{PKG_SLUG}}/types.ts",
11
+ "./settings/*": "./tinycld/{{PKG_SLUG}}/settings/*.tsx",
12
+ "./components/*": "./tinycld/{{PKG_SLUG}}/components/*.tsx",
13
+ "./hooks/*": "./tinycld/{{PKG_SLUG}}/hooks/*.ts"
14
+ },
15
+ "scripts": {
16
+ "typecheck": "tsc --noEmit --skipLibCheck"
17
+ },
18
+ "peerDependencies": {
19
+ "react": ">=19",
20
+ "react-native": ">=0.83",
21
+ "expo-router": ">=55.0.0"
22
+ }
23
+ }
@@ -0,0 +1,20 @@
1
+ import { useThemeColor } from '@tinycld/core/lib/use-app-theme'
2
+ import { ScrollView, Text, View } from 'react-native'
3
+
4
+ // The settings panel for {{PKG_NAME}}. Mounted by core inside
5
+ // /a/<orgSlug>/settings/{{PKG_SLUG}}/{{PKG_SLUG}} via the manifest's
6
+ // `settings[]` entry. This is the only screen the package contributes.
7
+
8
+ export default function {{PKG_PASCAL}}Settings() {
9
+ const fg = useThemeColor('foreground')
10
+ const muted = useThemeColor('muted-foreground')
11
+
12
+ return (
13
+ <ScrollView className="flex-1 bg-background">
14
+ <View className="p-6 gap-3">
15
+ <Text style={{ color: fg, fontSize: 22, fontWeight: '600' }}>{{PKG_NAME}}</Text>
16
+ <Text style={{ color: muted, fontSize: 14 }}>{{PKG_DESCRIPTION}}</Text>
17
+ </View>
18
+ </ScrollView>
19
+ )
20
+ }
@@ -0,0 +1,5 @@
1
+ // This package contributes only a settings panel — no pbtsdb collections,
2
+ // no schema extension. Keep this file for public type exports if any
3
+ // other package ever needs to import from {{PKG_SLUG}}.
4
+
5
+ export {}
@@ -0,0 +1,59 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ env:
10
+ APP_REPO: tinycld/tinycld
11
+ APP_REF: main
12
+
13
+ jobs:
14
+ checks:
15
+ name: Lint, typecheck, unit tests
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v5
19
+ with:
20
+ path: {{PKG_SLUG}}
21
+ - uses: actions/checkout@v5
22
+ with:
23
+ repository: ${{ env.APP_REPO }}
24
+ ref: ${{ env.APP_REF }}
25
+ path: tinycld
26
+ - uses: actions/setup-node@v5
27
+ with:
28
+ node-version-file: tinycld/.node-version
29
+ cache: npm
30
+ cache-dependency-path: tinycld/package-lock.json
31
+
32
+ # The app shell bundles @tinycld/core at packages/@tinycld/core/. This
33
+ # package's tsconfig path "@tinycld/core/*":
34
+ # ["../tinycld/packages/@tinycld/core/*"] and its server go.mod replace
35
+ # directive both point directly there.
36
+ - name: Install app shell deps
37
+ working-directory: tinycld
38
+ run: npm ci
39
+
40
+ - name: Link {{PKG_SLUG}} into tinycld app shell
41
+ working-directory: tinycld
42
+ run: npm run packages:link ../{{PKG_SLUG}}
43
+
44
+ - name: Lint (biome lives in tinycld and lints linked {{PKG_SLUG}} via app rules)
45
+ working-directory: tinycld
46
+ run: npm run lint
47
+
48
+ # Typecheck runs from inside tinycld so module resolution sees the
49
+ # full app-shell tree (uniwind augments, expo types, etc) — running
50
+ # tsc standalone in the package directory misses those.
51
+ - name: Typecheck (full app shell, package included via link)
52
+ working-directory: tinycld
53
+ env:
54
+ NODE_OPTIONS: --preserve-symlinks
55
+ run: npm run typecheck
56
+
57
+ - name: Run unit tests
58
+ working-directory: {{PKG_SLUG}}
59
+ run: npx vitest run
@@ -0,0 +1,54 @@
1
+ # {{PKG_SLUG}}
2
+
3
+ {{PKG_DESCRIPTION}}
4
+
5
+ Feature package for the [tinycld](https://github.com/tinycld/tinycld) ecosystem. Lives as a standalone git repo alongside the [`tinycld`](https://github.com/tinycld/tinycld) app shell and other sibling packages (`contacts`, `mail`, `calendar`, `drive`, `google-takeout-import`). The app shell bundles `@tinycld/core` inside it — there is no separate core repo to clone.
6
+
7
+ ## Development
8
+
9
+ ```sh
10
+ # Clone the app shell and this package as siblings
11
+ cd ~/code/tinycld
12
+ git clone git@github.com:tinycld/tinycld.git
13
+ git clone git@github.com:tinycld/{{PKG_SLUG}}.git
14
+
15
+ # Install deps in the app shell
16
+ cd tinycld
17
+ npm install
18
+
19
+ # Link this package into the app shell
20
+ npm run packages:link ../{{PKG_SLUG}}
21
+
22
+ # Run the full stack
23
+ npm run start
24
+ ```
25
+
26
+ ## Standalone checks
27
+
28
+ Lint and typecheck both run from the app shell — biome and TypeScript live
29
+ there, and the app shell's tsconfig pulls in `expo`'s base config, `uniwind`
30
+ type augments, and the live `~/types/pbSchema` generated from PocketBase,
31
+ none of which a standalone invocation in this package can see. Biome's
32
+ config lives in `tinycld/biome.json` and applies to every linked package
33
+ (there is no `biome.json` in this repo).
34
+
35
+ ```sh
36
+ cd ../tinycld
37
+ npm run packages:link ../{{PKG_SLUG}} # only needed once per checkout
38
+ npm run lint # scans this package via the app's biome rules
39
+ npm run typecheck # full app-shell tsc
40
+ ```
41
+
42
+ ## CI
43
+
44
+ `.github/workflows/ci.yml` runs lint, typecheck, and vitest on every push to
45
+ `main` and every PR. It clones `tinycld/tinycld@main` into a sibling
46
+ directory, installs the app shell's deps, links this package in, and runs
47
+ the checks — exactly what a developer does locally.
48
+
49
+ ## Package anatomy
50
+
51
+ - `manifest.ts` — the single source of truth for this package's capabilities
52
+ - `package.json` — name, exports map, peer deps
53
+ - `tsconfig.json` — typecheck config (lint config lives in the app shell's `biome.json`)
54
+ - `tests/` — vitest unit tests
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import manifest from '../manifest'
3
+
4
+ describe('{{PKG_SLUG}} manifest', () => {
5
+ it('declares required identifiers', () => {
6
+ expect(manifest.name).toBe('{{PKG_NAME}}')
7
+ expect(manifest.slug).toBe('{{PKG_SLUG}}')
8
+ expect(manifest.version).toMatch(/^\d+\.\d+\.\d+/)
9
+ })
10
+
11
+ it('has a description', () => {
12
+ expect(manifest.description).toBe('{{PKG_DESCRIPTION}}')
13
+ })
14
+ })