@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.
- package/README.md +297 -0
- package/dist/args.js +130 -0
- package/dist/args.js.map +1 -0
- package/dist/bootstrap-tooling.js +66 -0
- package/dist/bootstrap-tooling.js.map +1 -0
- package/dist/copy-template.js +96 -0
- package/dist/copy-template.js.map +1 -0
- package/dist/index.js +134 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.js +40 -0
- package/dist/layout.js.map +1 -0
- package/dist/link-core.js +99 -0
- package/dist/link-core.js.map +1 -0
- package/dist/link-package.js +85 -0
- package/dist/link-package.js.map +1 -0
- package/dist/prompts.js +187 -0
- package/dist/prompts.js.map +1 -0
- package/dist/substitute.js +35 -0
- package/dist/substitute.js.map +1 -0
- package/dist/validate.js +65 -0
- package/dist/validate.js.map +1 -0
- package/package.json +46 -0
- package/templates/full/manifest.ts +21 -0
- package/templates/full/package.json +30 -0
- package/templates/full/pb-migrations/1800000000_create_{{PKG_SLUG}}.js +67 -0
- package/templates/full/server/go.mod +10 -0
- package/templates/full/server/register.go +20 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/collections.ts +27 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/provider.tsx +16 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/screens/[id].tsx +23 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/screens/_layout.tsx +5 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/screens/index.tsx +28 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/seed.ts +22 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/sidebar.tsx +20 -0
- package/templates/full/tinycld/{{PKG_SLUG}}/types.ts +18 -0
- package/templates/settings-only/manifest.ts +15 -0
- package/templates/settings-only/package.json +23 -0
- package/templates/settings-only/tinycld/{{PKG_SLUG}}/settings/main.tsx +20 -0
- package/templates/settings-only/tinycld/{{PKG_SLUG}}/types.ts +5 -0
- package/templates/shared/.github/workflows/ci.yml +59 -0
- package/templates/shared/README.md +54 -0
- package/templates/shared/tests/manifest.test.ts +14 -0
- package/templates/shared/tsconfig.json +22 -0
package/dist/validate.js
ADDED
|
@@ -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":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAE3D,MAAM,OAAO,GAAG,2BAA2B,CAAA;AAC3C,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,UAAU,CAAC,CAAC,CAAA;AAC7F,MAAM,WAAW,GAAG,SAAS,CAAA;AAC7B,MAAM,OAAO,GAAG,mBAAmB,CAAA;AAEnC,MAAM,UAAU,YAAY,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,oCAAoC,CAAA;IACzE,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,qCAAqC,CAAA;IAClE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,0FAA0F,CAAA;IACrG,CAAC;IACD,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,IAAI,eAAe,CAAA;IAC5D,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,kBAAkB,CAAA;IAChE,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,qCAAqC,CAAA;IAClE,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC5C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,yBAAyB,CAAA;IACvE,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,6CAA6C,CAAA;IAC3E,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA,CAAC,WAAW;IACvD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,8DAA8D,CAAA;IAC9F,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IACrB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAAE,OAAO,8BAA8B,CAAA;IAC/D,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,oCAAoC,CAAA;IAChE,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACtC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA,CAAC,WAAW;IACjD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,4CAA4C,CAAA;IAC7E,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW;IACzC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC1B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;QAAE,OAAO,GAAG,GAAG,gCAAgC,CAAA;IACtE,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,CAAA;IACjE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,GAAG,GAAG,0BAA0B,CAAA;IAC/D,OAAO,IAAI,CAAA;AACf,CAAC"}
|
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,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,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,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
|
+
})
|