@luckystack/devkit 0.1.4 → 0.1.5

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.
@@ -1,136 +1,136 @@
1
- # Template customization
2
-
3
- `@luckystack/devkit` injects starter content into newly-created **empty** files
4
- under `src/` (a new `_api/*.ts`, `_sync/*.ts`, or `page.tsx`). Two things are
5
- customizable, both from the consumer's `.luckystack/templates/` folder:
6
-
7
- 1. **Which** template a file gets — the *selection rules*.
8
- 2. **What** that template contains — the *content*.
9
-
10
- Everything here is **dev-only**: devkit reads it from the hot-reload path during
11
- `npm run server`. Nothing ships to end users.
12
-
13
- ---
14
-
15
- ## How a file gets a template
16
-
17
- `injectTemplate(filePath)` (called by the file watcher on empty-file creation):
18
-
19
- 1. **Classify** the file structurally into a `fileKind`:
20
- `'api' | 'sync_server' | 'sync_client' | 'page'` (derived from the route
21
- conventions — override the markers via `registerRoutingRules`). For pages,
22
- placement is validated first; an un-routable placement gets a commented
23
- diagnostic instead of a real template.
24
- 2. **Select a kind** via `resolveTemplateKind(ctx)` — the registered rules are
25
- evaluated by descending `priority` (ties: newest registration first), and the
26
- first matching rule's `kind` wins.
27
- 3. **Resolve content** for that kind, first hit wins:
28
- - `.luckystack/templates/<kind>.template.ts(x)` — consumer file
29
- - a `registerTemplate('<kind>', '...')` string override
30
- - the bundled default shipped inside devkit (`dist/templates/`)
31
- 4. **Substitute placeholders** (`{{REL_PATH}}`, `{{PAGE_PATH}}`, `{{SYNC_NAME}}`)
32
- and write the file.
33
-
34
- The match context is:
35
-
36
- ```ts
37
- interface TemplateMatchContext {
38
- filePath: string; // absolute path of the new file
39
- fileKind: 'api' | 'sync_server' | 'sync_client' | 'page';
40
- hasPairedServer: boolean; // sync_client: does *_server_v<N>.ts exist?
41
- srcRelativePath: string | null; // path relative to src/, or null
42
- }
43
- ```
44
-
45
- ---
46
-
47
- ## The consumer overlay: `.luckystack/templates/`
48
-
49
- `create-luckystack-app` scaffolds this folder. devkit auto-loads
50
- `.luckystack/templates/templateRules.ts` **once, in dev, before the first
51
- injection** (a plain dynamic import — no wiring needed). Absent file ⇒ devkit's
52
- built-in defaults apply.
53
-
54
- ```
55
- .luckystack/templates/
56
- templateRules.ts # the selection logic — edit/remove/add rules
57
- api.template.ts # editable copies of the built-in template bodies
58
- sync_server.template.ts
59
- sync_client_paired.template.ts
60
- sync_client_standalone.template.ts
61
- page_plain.template.tsx
62
- page_dashboard.template.tsx
63
- README.md
64
- ```
65
-
66
- A `*.template.*` file in this folder overrides that kind's content. **Delete** a
67
- file to fall back to devkit's (upgradeable) bundled default. The shipped copies
68
- are a snapshot of the defaults at scaffold time — to refresh, copy from
69
- `node_modules/@luckystack/devkit/dist/templates/`.
70
-
71
- ---
72
-
73
- ## API
74
-
75
- | Export | Purpose |
76
- |---|---|
77
- | `registerTemplateRule({ kind, match, priority? })` | Add a selection rule. `priority` default for direct calls is the caller's; defaults use 10 (specific) / 0 (page catch-all). |
78
- | `registerTemplateKind(kind, { match, content?, priority? })` | Register a brand-new kind (rule + optional inline content) in one call. Default priority 100 so custom kinds beat the built-ins. |
79
- | `registerTemplate(kind, content)` | Override just the content body of a kind (string). |
80
- | `resolveTemplateKind(ctx)` | Evaluate the active rules → the chosen kind (or null). |
81
- | `getTemplateRules()` | The active rules in evaluation order. |
82
- | `clearTemplateRules()` | Drop ALL rules — including the built-in defaults. The scaffolded `templateRules.ts` calls this first so it is the single source of truth. |
83
- | `registerDefaultTemplateRules()` | (Re)arm the framework defaults. Armed automatically on module load; idempotent. |
84
- | `BUILT_IN_TEMPLATE_KINDS` / `BUILT_IN_TEMPLATE_FILENAMES` / `DEFAULT_DASHBOARD_PATH_PATTERN` | The 6 kinds, their bundled filenames, and the page-dashboard heuristic regex. |
85
-
86
- Built-in kinds: `api`, `sync_server`, `sync_client_paired`,
87
- `sync_client_standalone`, `page_plain`, `page_dashboard`.
88
-
89
- ---
90
-
91
- ## Recipes
92
-
93
- ### Change when a page gets the dashboard layout
94
-
95
- Edit the `page_dashboard` rule in `.luckystack/templates/templateRules.ts`:
96
-
97
- ```ts
98
- registerTemplateRule({
99
- kind: 'page_dashboard',
100
- priority: 10,
101
- match: (ctx) => ctx.fileKind === 'page' && ctx.filePath.replaceAll('\\', '/').includes('/app/'),
102
- });
103
- ```
104
-
105
- ### Remove a rule entirely
106
-
107
- The scaffolded `templateRules.ts` starts with `clearTemplateRules()` and then
108
- re-declares each default. Delete the rule you don't want — that mapping no
109
- longer applies (a page with no matching rule simply isn't injected).
110
-
111
- ### Add a brand-new template kind
112
-
113
- ```ts
114
- // .luckystack/templates/templateRules.ts
115
- import { registerTemplateKind } from '@luckystack/devkit';
116
-
117
- registerTemplateKind('page_marketing', {
118
- priority: 20, // beats page_dashboard / page_plain
119
- match: (ctx) => ctx.fileKind === 'page' && ctx.filePath.replaceAll('\\', '/').includes('/marketing/'),
120
- });
121
- ```
122
-
123
- Then create `.luckystack/templates/page_marketing.template.tsx` with the body.
124
- Custom kinds resolve content from `<kind>.template.tsx` then `<kind>.template.ts`
125
- in the overlay folder (or a `registerTemplate('page_marketing', '...')` string).
126
-
127
- ---
128
-
129
- ## Packaging note (framework maintainers)
130
-
131
- The bundled templates live at `packages/devkit/src/templates/`. They are read at
132
- runtime via `fs.readFileSync(dist/templates/...)`, so `tsup.config.ts` copies
133
- `src/templates → dist/templates` in its `onSuccess` hook and `files: ["dist"]`
134
- ships them in the tarball. The scaffolded consumer copies live separately under
135
- `create-luckystack-app/template/_dot_luckystack/templates/` (the `_dot_` prefix
136
- is rewritten to `.` by the scaffold so npm doesn't drop the dot-folder).
1
+ # Template customization
2
+
3
+ `@luckystack/devkit` injects starter content into newly-created **empty** files
4
+ under `src/` (a new `_api/*.ts`, `_sync/*.ts`, or `page.tsx`). Two things are
5
+ customizable, both from the consumer's `.luckystack/templates/` folder:
6
+
7
+ 1. **Which** template a file gets — the *selection rules*.
8
+ 2. **What** that template contains — the *content*.
9
+
10
+ Everything here is **dev-only**: devkit reads it from the hot-reload path during
11
+ `npm run server`. Nothing ships to end users.
12
+
13
+ ---
14
+
15
+ ## How a file gets a template
16
+
17
+ `injectTemplate(filePath)` (called by the file watcher on empty-file creation):
18
+
19
+ 1. **Classify** the file structurally into a `fileKind`:
20
+ `'api' | 'sync_server' | 'sync_client' | 'page'` (derived from the route
21
+ conventions — override the markers via `registerRoutingRules`). For pages,
22
+ placement is validated first; an un-routable placement gets a commented
23
+ diagnostic instead of a real template.
24
+ 2. **Select a kind** via `resolveTemplateKind(ctx)` — the registered rules are
25
+ evaluated by descending `priority` (ties: newest registration first), and the
26
+ first matching rule's `kind` wins.
27
+ 3. **Resolve content** for that kind, first hit wins:
28
+ - `.luckystack/templates/<kind>.template.ts(x)` — consumer file
29
+ - a `registerTemplate('<kind>', '...')` string override
30
+ - the bundled default shipped inside devkit (`dist/templates/`)
31
+ 4. **Substitute placeholders** (`{{REL_PATH}}`, `{{PAGE_PATH}}`, `{{SYNC_NAME}}`)
32
+ and write the file.
33
+
34
+ The match context is:
35
+
36
+ ```ts
37
+ interface TemplateMatchContext {
38
+ filePath: string; // absolute path of the new file
39
+ fileKind: 'api' | 'sync_server' | 'sync_client' | 'page';
40
+ hasPairedServer: boolean; // sync_client: does *_server_v<N>.ts exist?
41
+ srcRelativePath: string | null; // path relative to src/, or null
42
+ }
43
+ ```
44
+
45
+ ---
46
+
47
+ ## The consumer overlay: `.luckystack/templates/`
48
+
49
+ `create-luckystack-app` scaffolds this folder. devkit auto-loads
50
+ `.luckystack/templates/templateRules.ts` **once, in dev, before the first
51
+ injection** (a plain dynamic import — no wiring needed). Absent file ⇒ devkit's
52
+ built-in defaults apply.
53
+
54
+ ```
55
+ .luckystack/templates/
56
+ templateRules.ts # the selection logic — edit/remove/add rules
57
+ api.template.ts # editable copies of the built-in template bodies
58
+ sync_server.template.ts
59
+ sync_client_paired.template.ts
60
+ sync_client_standalone.template.ts
61
+ page_plain.template.tsx
62
+ page_dashboard.template.tsx
63
+ README.md
64
+ ```
65
+
66
+ A `*.template.*` file in this folder overrides that kind's content. **Delete** a
67
+ file to fall back to devkit's (upgradeable) bundled default. The shipped copies
68
+ are a snapshot of the defaults at scaffold time — to refresh, copy from
69
+ `node_modules/@luckystack/devkit/dist/templates/`.
70
+
71
+ ---
72
+
73
+ ## API
74
+
75
+ | Export | Purpose |
76
+ |---|---|
77
+ | `registerTemplateRule({ kind, match, priority? })` | Add a selection rule. `priority` default for direct calls is the caller's; defaults use 10 (specific) / 0 (page catch-all). |
78
+ | `registerTemplateKind(kind, { match, content?, priority? })` | Register a brand-new kind (rule + optional inline content) in one call. Default priority 100 so custom kinds beat the built-ins. |
79
+ | `registerTemplate(kind, content)` | Override just the content body of a kind (string). |
80
+ | `resolveTemplateKind(ctx)` | Evaluate the active rules → the chosen kind (or null). |
81
+ | `getTemplateRules()` | The active rules in evaluation order. |
82
+ | `clearTemplateRules()` | Drop ALL rules — including the built-in defaults. The scaffolded `templateRules.ts` calls this first so it is the single source of truth. |
83
+ | `registerDefaultTemplateRules()` | (Re)arm the framework defaults. Armed automatically on module load; idempotent. |
84
+ | `BUILT_IN_TEMPLATE_KINDS` / `BUILT_IN_TEMPLATE_FILENAMES` / `DEFAULT_DASHBOARD_PATH_PATTERN` | The 6 kinds, their bundled filenames, and the page-dashboard heuristic regex. |
85
+
86
+ Built-in kinds: `api`, `sync_server`, `sync_client_paired`,
87
+ `sync_client_standalone`, `page_plain`, `page_dashboard`.
88
+
89
+ ---
90
+
91
+ ## Recipes
92
+
93
+ ### Change when a page gets the dashboard layout
94
+
95
+ Edit the `page_dashboard` rule in `.luckystack/templates/templateRules.ts`:
96
+
97
+ ```ts
98
+ registerTemplateRule({
99
+ kind: 'page_dashboard',
100
+ priority: 10,
101
+ match: (ctx) => ctx.fileKind === 'page' && ctx.filePath.replaceAll('\\', '/').includes('/app/'),
102
+ });
103
+ ```
104
+
105
+ ### Remove a rule entirely
106
+
107
+ The scaffolded `templateRules.ts` starts with `clearTemplateRules()` and then
108
+ re-declares each default. Delete the rule you don't want — that mapping no
109
+ longer applies (a page with no matching rule simply isn't injected).
110
+
111
+ ### Add a brand-new template kind
112
+
113
+ ```ts
114
+ // .luckystack/templates/templateRules.ts
115
+ import { registerTemplateKind } from '@luckystack/devkit';
116
+
117
+ registerTemplateKind('page_marketing', {
118
+ priority: 20, // beats page_dashboard / page_plain
119
+ match: (ctx) => ctx.fileKind === 'page' && ctx.filePath.replaceAll('\\', '/').includes('/marketing/'),
120
+ });
121
+ ```
122
+
123
+ Then create `.luckystack/templates/page_marketing.template.tsx` with the body.
124
+ Custom kinds resolve content from `<kind>.template.tsx` then `<kind>.template.ts`
125
+ in the overlay folder (or a `registerTemplate('page_marketing', '...')` string).
126
+
127
+ ---
128
+
129
+ ## Packaging note (framework maintainers)
130
+
131
+ The bundled templates live at `packages/devkit/src/templates/`. They are read at
132
+ runtime via `fs.readFileSync(dist/templates/...)`, so `tsup.config.ts` copies
133
+ `src/templates → dist/templates` in its `onSuccess` hook and `files: ["dist"]`
134
+ ships them in the tarball. The scaffolded consumer copies live separately under
135
+ `create-luckystack-app/template/_dot_luckystack/templates/` (the `_dot_` prefix
136
+ is rewritten to `.` by the scaffold so npm doesn't drop the dot-folder).