everything-dev 1.12.4 → 1.14.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 (141) hide show
  1. package/dist/app.cjs +17 -5
  2. package/dist/app.cjs.map +1 -1
  3. package/dist/app.mjs +17 -5
  4. package/dist/app.mjs.map +1 -1
  5. package/dist/cli/init.cjs +151 -74
  6. package/dist/cli/init.cjs.map +1 -1
  7. package/dist/cli/init.d.cts +1 -1
  8. package/dist/cli/init.d.cts.map +1 -1
  9. package/dist/cli/init.d.mts +1 -1
  10. package/dist/cli/init.d.mts.map +1 -1
  11. package/dist/cli/init.mjs +152 -75
  12. package/dist/cli/init.mjs.map +1 -1
  13. package/dist/cli/prompts.cjs +3 -3
  14. package/dist/cli/prompts.cjs.map +1 -1
  15. package/dist/cli/prompts.mjs +3 -3
  16. package/dist/cli/prompts.mjs.map +1 -1
  17. package/dist/cli/status.cjs +22 -0
  18. package/dist/cli/status.cjs.map +1 -1
  19. package/dist/cli/status.mjs +22 -0
  20. package/dist/cli/status.mjs.map +1 -1
  21. package/dist/cli/sync.cjs +15 -56
  22. package/dist/cli/sync.cjs.map +1 -1
  23. package/dist/cli/sync.mjs +15 -56
  24. package/dist/cli/sync.mjs.map +1 -1
  25. package/dist/cli/upgrade.cjs +59 -5
  26. package/dist/cli/upgrade.cjs.map +1 -1
  27. package/dist/cli/upgrade.mjs +59 -5
  28. package/dist/cli/upgrade.mjs.map +1 -1
  29. package/dist/cli.cjs +1 -1
  30. package/dist/cli.cjs.map +1 -1
  31. package/dist/cli.mjs +1 -1
  32. package/dist/cli.mjs.map +1 -1
  33. package/dist/config.cjs +206 -69
  34. package/dist/config.cjs.map +1 -1
  35. package/dist/config.d.cts +13 -6
  36. package/dist/config.d.cts.map +1 -1
  37. package/dist/config.d.mts +13 -6
  38. package/dist/config.d.mts.map +1 -1
  39. package/dist/config.mjs +201 -71
  40. package/dist/config.mjs.map +1 -1
  41. package/dist/contract.d.cts +106 -10
  42. package/dist/contract.d.cts.map +1 -1
  43. package/dist/contract.d.mts +106 -10
  44. package/dist/contract.d.mts.map +1 -1
  45. package/dist/host.cjs +34 -1
  46. package/dist/host.cjs.map +1 -1
  47. package/dist/host.d.cts.map +1 -1
  48. package/dist/host.d.mts.map +1 -1
  49. package/dist/host.mjs +34 -1
  50. package/dist/host.mjs.map +1 -1
  51. package/dist/index.cjs +16 -0
  52. package/dist/index.d.cts +5 -3
  53. package/dist/index.d.mts +5 -3
  54. package/dist/index.mjs +5 -3
  55. package/dist/internal/manifest-normalizer.cjs +14 -1
  56. package/dist/internal/manifest-normalizer.cjs.map +1 -1
  57. package/dist/internal/manifest-normalizer.mjs +14 -1
  58. package/dist/internal/manifest-normalizer.mjs.map +1 -1
  59. package/dist/merge.cjs +113 -0
  60. package/dist/merge.cjs.map +1 -0
  61. package/dist/merge.d.cts +7 -0
  62. package/dist/merge.d.cts.map +1 -0
  63. package/dist/merge.d.mts +7 -0
  64. package/dist/merge.d.mts.map +1 -0
  65. package/dist/merge.mjs +107 -0
  66. package/dist/merge.mjs.map +1 -0
  67. package/dist/orchestrator.d.cts +1 -1
  68. package/dist/orchestrator.d.mts +1 -1
  69. package/dist/plugin.cjs +117 -105
  70. package/dist/plugin.cjs.map +1 -1
  71. package/dist/plugin.d.cts +115 -9
  72. package/dist/plugin.d.cts.map +1 -1
  73. package/dist/plugin.d.mts +115 -9
  74. package/dist/plugin.d.mts.map +1 -1
  75. package/dist/plugin.mjs +117 -105
  76. package/dist/plugin.mjs.map +1 -1
  77. package/dist/service-descriptor.cjs +21 -0
  78. package/dist/service-descriptor.cjs.map +1 -1
  79. package/dist/service-descriptor.d.cts +23 -1
  80. package/dist/service-descriptor.d.cts.map +1 -1
  81. package/dist/service-descriptor.d.mts +23 -1
  82. package/dist/service-descriptor.d.mts.map +1 -1
  83. package/dist/service-descriptor.mjs +21 -0
  84. package/dist/service-descriptor.mjs.map +1 -1
  85. package/dist/shared.cjs +24 -2
  86. package/dist/shared.cjs.map +1 -1
  87. package/dist/shared.d.cts +3 -0
  88. package/dist/shared.d.cts.map +1 -1
  89. package/dist/shared.d.mts +3 -0
  90. package/dist/shared.d.mts.map +1 -1
  91. package/dist/shared.mjs +25 -3
  92. package/dist/shared.mjs.map +1 -1
  93. package/dist/sidebar.cjs +124 -0
  94. package/dist/sidebar.cjs.map +1 -0
  95. package/dist/sidebar.d.cts +8 -0
  96. package/dist/sidebar.d.cts.map +1 -0
  97. package/dist/sidebar.d.mts +8 -0
  98. package/dist/sidebar.d.mts.map +1 -0
  99. package/dist/sidebar.mjs +122 -0
  100. package/dist/sidebar.mjs.map +1 -0
  101. package/dist/types.cjs +104 -10
  102. package/dist/types.cjs.map +1 -1
  103. package/dist/types.d.cts +256 -29
  104. package/dist/types.d.cts.map +1 -1
  105. package/dist/types.d.mts +256 -29
  106. package/dist/types.d.mts.map +1 -1
  107. package/dist/types.mjs +100 -11
  108. package/dist/types.mjs.map +1 -1
  109. package/dist/utils/path-match.cjs +18 -0
  110. package/dist/utils/path-match.cjs.map +1 -0
  111. package/dist/utils/path-match.mjs +17 -0
  112. package/dist/utils/path-match.mjs.map +1 -0
  113. package/dist/utils/save-config.cjs +19 -0
  114. package/dist/utils/save-config.cjs.map +1 -0
  115. package/dist/utils/save-config.mjs +18 -0
  116. package/dist/utils/save-config.mjs.map +1 -0
  117. package/package.json +6 -6
  118. package/skills/dev-workflow/SKILL.md +8 -0
  119. package/skills/extends-config/SKILL.md +132 -0
  120. package/skills/init-upgrade/SKILL.md +128 -0
  121. package/skills/publish-sync/SKILL.md +30 -0
  122. package/src/app.ts +15 -5
  123. package/src/cli/init.ts +207 -108
  124. package/src/cli/prompts.ts +2 -2
  125. package/src/cli/status.ts +20 -0
  126. package/src/cli/sync.ts +27 -96
  127. package/src/cli/upgrade.ts +65 -3
  128. package/src/cli.ts +1 -1
  129. package/src/config.ts +306 -119
  130. package/src/host.ts +45 -0
  131. package/src/index.ts +1 -0
  132. package/src/internal/manifest-normalizer.ts +16 -4
  133. package/src/merge.ts +198 -0
  134. package/src/plugin.ts +340 -318
  135. package/src/service-descriptor.ts +23 -0
  136. package/src/shared.ts +48 -5
  137. package/src/sidebar.ts +162 -0
  138. package/src/types.ts +134 -28
  139. package/src/utils/path-match.ts +16 -0
  140. package/src/utils/save-config.ts +20 -0
  141. package/cli.js +0 -10
@@ -0,0 +1,18 @@
1
+
2
+ //#region src/utils/path-match.ts
3
+ function isPathExcluded(filePath, excludePatterns) {
4
+ if (excludePatterns.length === 0) return false;
5
+ for (const pattern of excludePatterns) if (pattern.endsWith("/**")) {
6
+ const prefix = pattern.slice(0, -3);
7
+ if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
8
+ } else if (pattern.endsWith("/*")) {
9
+ const prefix = pattern.slice(0, -2);
10
+ const slashIdx = filePath.indexOf("/", prefix.length + 1);
11
+ if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;
12
+ } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) return true;
13
+ return false;
14
+ }
15
+
16
+ //#endregion
17
+ exports.isPathExcluded = isPathExcluded;
18
+ //# sourceMappingURL=path-match.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-match.cjs","names":[],"sources":["../../src/utils/path-match.ts"],"sourcesContent":["export function isPathExcluded(filePath: string, excludePatterns: string[]): boolean {\n if (excludePatterns.length === 0) return false;\n for (const pattern of excludePatterns) {\n if (pattern.endsWith(\"/**\")) {\n const prefix = pattern.slice(0, -3);\n if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;\n } else if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n const slashIdx = filePath.indexOf(\"/\", prefix.length + 1);\n if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;\n } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";;AAAA,SAAgB,eAAe,UAAkB,iBAAoC;AACnF,KAAI,gBAAgB,WAAW,EAAG,QAAO;AACzC,MAAK,MAAM,WAAW,gBACpB,KAAI,QAAQ,SAAS,MAAM,EAAE;EAC3B,MAAM,SAAS,QAAQ,MAAM,GAAG,GAAG;AACnC,MAAI,SAAS,WAAW,GAAG,OAAO,GAAG,IAAI,aAAa,OAAQ,QAAO;YAC5D,QAAQ,SAAS,KAAK,EAAE;EACjC,MAAM,SAAS,QAAQ,MAAM,GAAG,GAAG;EACnC,MAAM,WAAW,SAAS,QAAQ,KAAK,OAAO,SAAS,EAAE;AACzD,MAAI,SAAS,WAAW,GAAG,OAAO,GAAG,IAAI,aAAa,GAAI,QAAO;YACxD,aAAa,WAAW,SAAS,WAAW,GAAG,QAAQ,GAAG,CACnE,QAAO;AAGX,QAAO"}
@@ -0,0 +1,17 @@
1
+ //#region src/utils/path-match.ts
2
+ function isPathExcluded(filePath, excludePatterns) {
3
+ if (excludePatterns.length === 0) return false;
4
+ for (const pattern of excludePatterns) if (pattern.endsWith("/**")) {
5
+ const prefix = pattern.slice(0, -3);
6
+ if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
7
+ } else if (pattern.endsWith("/*")) {
8
+ const prefix = pattern.slice(0, -2);
9
+ const slashIdx = filePath.indexOf("/", prefix.length + 1);
10
+ if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;
11
+ } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) return true;
12
+ return false;
13
+ }
14
+
15
+ //#endregion
16
+ export { isPathExcluded };
17
+ //# sourceMappingURL=path-match.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-match.mjs","names":[],"sources":["../../src/utils/path-match.ts"],"sourcesContent":["export function isPathExcluded(filePath: string, excludePatterns: string[]): boolean {\n if (excludePatterns.length === 0) return false;\n for (const pattern of excludePatterns) {\n if (pattern.endsWith(\"/**\")) {\n const prefix = pattern.slice(0, -3);\n if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;\n } else if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n const slashIdx = filePath.indexOf(\"/\", prefix.length + 1);\n if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;\n } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":";AAAA,SAAgB,eAAe,UAAkB,iBAAoC;AACnF,KAAI,gBAAgB,WAAW,EAAG,QAAO;AACzC,MAAK,MAAM,WAAW,gBACpB,KAAI,QAAQ,SAAS,MAAM,EAAE;EAC3B,MAAM,SAAS,QAAQ,MAAM,GAAG,GAAG;AACnC,MAAI,SAAS,WAAW,GAAG,OAAO,GAAG,IAAI,aAAa,OAAQ,QAAO;YAC5D,QAAQ,SAAS,KAAK,EAAE;EACjC,MAAM,SAAS,QAAQ,MAAM,GAAG,GAAG;EACnC,MAAM,WAAW,SAAS,QAAQ,KAAK,OAAO,SAAS,EAAE;AACzD,MAAI,SAAS,WAAW,GAAG,OAAO,GAAG,IAAI,aAAa,GAAI,QAAO;YACxD,aAAa,WAAW,SAAS,WAAW,GAAG,QAAQ,GAAG,CACnE,QAAO;AAGX,QAAO"}
@@ -0,0 +1,19 @@
1
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_merge = require('../merge.cjs');
3
+ let node_fs = require("node:fs");
4
+ let node_path = require("node:path");
5
+
6
+ //#region src/utils/save-config.ts
7
+ async function saveBosConfig(configDir, config) {
8
+ const filePath = (0, node_path.join)(configDir, "bos.config.json");
9
+ const ordered = require_merge.rebuildOrderedConfig(config);
10
+ const next = `${JSON.stringify(ordered, null, 2)}\n`;
11
+ try {
12
+ if ((0, node_fs.readFileSync)(filePath, "utf8") === next) return;
13
+ } catch {}
14
+ (0, node_fs.writeFileSync)(filePath, next);
15
+ }
16
+
17
+ //#endregion
18
+ exports.saveBosConfig = saveBosConfig;
19
+ //# sourceMappingURL=save-config.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save-config.cjs","names":["rebuildOrderedConfig"],"sources":["../../src/utils/save-config.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { rebuildOrderedConfig } from \"../merge\";\nimport type { BosConfig } from \"../types\";\n\nexport async function saveBosConfig(\n configDir: string,\n config: BosConfig | Record<string, unknown>,\n): Promise<void> {\n const filePath = join(configDir, \"bos.config.json\");\n const ordered = rebuildOrderedConfig(config as Record<string, unknown>);\n const next = `${JSON.stringify(ordered, null, 2)}\\n`;\n try {\n if (readFileSync(filePath, \"utf8\") === next) return;\n } catch {\n // file does not exist yet\n }\n\n writeFileSync(filePath, next);\n}\n"],"mappings":";;;;;;AAKA,eAAsB,cACpB,WACA,QACe;CACf,MAAM,+BAAgB,WAAW,kBAAkB;CACnD,MAAM,UAAUA,mCAAqB,OAAkC;CACvE,MAAM,OAAO,GAAG,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AACjD,KAAI;AACF,gCAAiB,UAAU,OAAO,KAAK,KAAM;SACvC;AAIR,4BAAc,UAAU,KAAK"}
@@ -0,0 +1,18 @@
1
+ import { rebuildOrderedConfig } from "../merge.mjs";
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ //#region src/utils/save-config.ts
6
+ async function saveBosConfig(configDir, config) {
7
+ const filePath = join(configDir, "bos.config.json");
8
+ const ordered = rebuildOrderedConfig(config);
9
+ const next = `${JSON.stringify(ordered, null, 2)}\n`;
10
+ try {
11
+ if (readFileSync(filePath, "utf8") === next) return;
12
+ } catch {}
13
+ writeFileSync(filePath, next);
14
+ }
15
+
16
+ //#endregion
17
+ export { saveBosConfig };
18
+ //# sourceMappingURL=save-config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save-config.mjs","names":[],"sources":["../../src/utils/save-config.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { rebuildOrderedConfig } from \"../merge\";\nimport type { BosConfig } from \"../types\";\n\nexport async function saveBosConfig(\n configDir: string,\n config: BosConfig | Record<string, unknown>,\n): Promise<void> {\n const filePath = join(configDir, \"bos.config.json\");\n const ordered = rebuildOrderedConfig(config as Record<string, unknown>);\n const next = `${JSON.stringify(ordered, null, 2)}\\n`;\n try {\n if (readFileSync(filePath, \"utf8\") === next) return;\n } catch {\n // file does not exist yet\n }\n\n writeFileSync(filePath, next);\n}\n"],"mappings":";;;;;AAKA,eAAsB,cACpB,WACA,QACe;CACf,MAAM,WAAW,KAAK,WAAW,kBAAkB;CACnD,MAAM,UAAU,qBAAqB,OAAkC;CACvE,MAAM,OAAO,GAAG,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC;AACjD,KAAI;AACF,MAAI,aAAa,UAAU,OAAO,KAAK,KAAM;SACvC;AAIR,eAAc,UAAU,KAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "everything-dev",
3
- "version": "1.12.4",
3
+ "version": "1.14.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -116,13 +116,12 @@
116
116
  "files": [
117
117
  "dist",
118
118
  "src",
119
- "cli.js",
120
119
  "skills",
121
120
  "!skills/_artifacts"
122
121
  ],
123
122
  "bin": {
124
- "everything-dev": "./cli.js",
125
- "bos": "./cli.js"
123
+ "everything-dev": "./dist/cli.mjs",
124
+ "bos": "./dist/cli.mjs"
126
125
  },
127
126
  "keywords": [
128
127
  "tanstack-intent"
@@ -154,11 +153,12 @@
154
153
  "@orpc/server": "^1.13.4",
155
154
  "@orpc/zod": "^1.13.4",
156
155
  "chalk": "^5.6.2",
156
+ "defu": "^6.1.7",
157
157
  "effect": "^3.21.0",
158
- "every-plugin": "^2.5.8",
158
+ "every-plugin": "^2.5.9",
159
159
  "glob": "^13.0.6",
160
160
  "gradient-string": "^3.0.0",
161
- "hono": "^4.7.11",
161
+ "hono": "^4.12.18",
162
162
  "ink": "^6.8.0",
163
163
  "tar": "^7.4.3"
164
164
  },
@@ -86,6 +86,14 @@ On page refresh:
86
86
 
87
87
  This means a new deployment requires a host restart to pick up new URLs.
88
88
 
89
+ ### Resolved Config (`.bos/bos.resolved-config.json`)
90
+
91
+ `bos dev` and `bos build` write the fully-merged config to `.bos/bos.resolved-config.json` (gitignored). This file includes `_resolved` metadata with env, timestamp, and extends chain.
92
+
93
+ **`bos.config.json` is NOT modified during dev.** Only `bos publish --deploy`, `bos plugin publish/add/remove`, and `bos sync` write to `bos.config.json`.
94
+
95
+ Build configs (rsbuild/rspack) read from `.bos/bos.resolved-config.json` first, falling back to `bos.config.json`. This allows slim child configs with `extends` to work correctly — the merged parent+child config is what the build sees.
96
+
89
97
  ## Debugging
90
98
 
91
99
  ```bash
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: extends-config
3
+ description: How bos.config.json extends chains work, deep merge semantics, resolved config lifecycle, env-specific extends, and canonical field ordering. Use when debugging extends inheritance, configuring per-environment parents, understanding what dev writes vs publish writes, or reasoning about config merging.
4
+ metadata:
5
+ sources: "src/merge.ts,src/config.ts,src/shared.ts,src/types.ts"
6
+ ---
7
+
8
+ # extends & Config Merging
9
+
10
+ ## extends Field
11
+
12
+ The `extends` field in `bos.config.json` specifies a parent config to inherit from. Supports two forms:
13
+
14
+ ### String (all environments use same parent)
15
+ ```json
16
+ { "extends": "bos://dev.everything.near/everything.dev" }
17
+ ```
18
+
19
+ ### Object (per-environment parent)
20
+ ```json
21
+ {
22
+ "extends": {
23
+ "development": "bos://dev.everything.near/everything.dev",
24
+ "production": "bos://dev.everything.near/everything.dev",
25
+ "staging": "bos://staging.everything.near/everything.dev"
26
+ }
27
+ }
28
+ ```
29
+
30
+ Fallback chain: requested env → `production` → first defined value.
31
+
32
+ ## Deep Merge Semantics
33
+
34
+ Uses `defu` (with `createDefu` for custom merge rules):
35
+
36
+ | Field type | Merge behavior |
37
+ |-----------|---------------|
38
+ | Scalars (account, domain, repository) | Child overrides parent; parent inherited when child omits |
39
+ | `shared.ui.*` dep entries | Deep merged — child overrides specific keys, parent deps preserved |
40
+ | `plugins` | Deep merged — child overrides per-key, parent plugins preserved unless removed |
41
+ | `secrets` arrays | Unioned (deduplicated) |
42
+ | `routes` arrays | Child replaces parent |
43
+ | `variables` | Deep merged per-key |
44
+
45
+ ### Null Sentinel Removal
46
+
47
+ Set a plugin to `null` to explicitly remove an inherited plugin:
48
+ ```json
49
+ {
50
+ "plugins": {
51
+ "template": null
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Resolved Config: `.bos/bos.resolved-config.json`
57
+
58
+ **Generated by**: `bos dev`, `bos build`, `syncAndGenerateSharedUi()`
59
+ **Gitignored**: Yes (inside `.bos/`)
60
+
61
+ When `bos dev` or `bos build` runs:
62
+ 1. The full extends chain is resolved in memory
63
+ 2. The merged config is written to `.bos/bos.resolved-config.json`
64
+ 3. **`bos.config.json` is NOT modified** during dev
65
+
66
+ Structure:
67
+ ```json
68
+ {
69
+ "_resolved": {
70
+ "env": "development",
71
+ "resolvedAt": "2026-05-11T...",
72
+ "extendsChain": ["bos://dev.everything.near/everything.dev"]
73
+ },
74
+ "account": "me.near",
75
+ "domain": "my.dev",
76
+ "shared": { ... },
77
+ "app": { ... },
78
+ "plugins": { ... }
79
+ }
80
+ ```
81
+
82
+ ### Build configs read resolved config first
83
+
84
+ All build configs (ui/rsbuild.config.ts, host/rsbuild.config.ts, api/rspack.config.js, plugins/*/rspack.config.js) try `.bos/bos.resolved-config.json` first, falling back to `bos.config.json`.
85
+
86
+ The `_resolved` metadata is stripped before use.
87
+
88
+ ### When bos.config.json IS written
89
+
90
+ | Command | Writes bos.config.json? | Why |
91
+ |---------|------------------------|-----|
92
+ | `bos dev` | No | Uses resolved config |
93
+ | `bos build` | No | Uses resolved config |
94
+ | `bos publish --deploy` | Yes | Snapshot moment — pins production URLs + versions |
95
+ | `bos plugin publish` | Yes | Records production URL + integrity |
96
+ | `bos plugin add/remove` | Yes | Changes project's own plugin list |
97
+ | `bos sync` | Yes | Merges template updates into local config |
98
+
99
+ ### Remote host mode (bos->catalog)
100
+
101
+ When host is remote, `syncAndGenerateSharedUi()` reads versions from `bos.config.json` and writes them into `package.json` catalog. No resolved config is written — the remote host reads `bos.config.json` directly.
102
+
103
+ ## Canonical Field Ordering
104
+
105
+ `BOS_CONFIG_ORDER` enforces consistent key order:
106
+
107
+ 1. `extends` — always first
108
+ 2. `account`
109
+ 3. `domain`
110
+ 4. `testnet`
111
+ 5. `staging`
112
+ 6. `repository`
113
+ 7. `app`
114
+ 8. `plugins`
115
+ 9. `shared`
116
+
117
+ Unknown keys go after known keys. `rebuildOrderedConfig()` is applied before every write.
118
+
119
+ ## API
120
+
121
+ From `src/config.ts`:
122
+ - `writeResolvedConfig(configDir, config, env, extendsChain?)` — writes `.bos/bos.resolved-config.json`
123
+ - `loadResolvedConfig(configDir)` — reads resolved config, returns `BosConfig | null`
124
+ - `resolveBosConfigPath(configDir)` — returns resolved config path if exists, else `bos.config.json`
125
+ - `readBosConfigForBuild(configDir)` — reads resolved config stripping `_resolved`, falls back to `bos.config.json`
126
+
127
+ From `src/merge.ts`:
128
+ - `mergeBosConfigWithExtends(parent, child)` — deep merge for extends chain
129
+ - `mergeBosConfigWithTemplate(local, template)` — merge for sync (local wins)
130
+ - `resolveExtendsRef(extendsField, env)` — resolve string|object extends for a given env
131
+ - `rebuildOrderedConfig(config)` — enforce canonical ordering
132
+ - `BOS_CONFIG_ORDER` — ordered field names
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: init-upgrade
3
+ description: bos init, bos sync, and bos upgrade workflows — template download, snapshot-based conflict detection, package version bumps, and what .templatekeep/.templatesync-exclude control. Use when scaffolding new projects, syncing upstream changes, or upgrading framework packages.
4
+ metadata:
5
+ sources: "src/cli/init.ts,src/cli/sync.ts,src/cli/upgrade.ts,src/cli/snapshot.ts"
6
+ ---
7
+
8
+ # bos init, sync, upgrade
9
+
10
+ ## bos init
11
+
12
+ Creates a new project from a published template:
13
+
14
+ ```bash
15
+ bos init # Interactive
16
+ bos init -a my.near --domain my.dev # Skip prompts
17
+ bos init --with-host # Include host in workspace
18
+ ```
19
+
20
+ ### Flow
21
+ 1. Fetch parent config from FastKV (`bos://account/gateway`)
22
+ 2. Download template tarball from parent's `repository` URL
23
+ 3. Copy files matching `.templatekeep` patterns
24
+ 4. Filter plugins: only included plugins + their routes are copied
25
+ 5. `personalizeConfig()` — sets `extends`, `account`, `domain`, removes production URLs
26
+ 6. `resolveWorkspaceRefs()` — normalizes package manifests, sets catalog refs
27
+ 7. Write initial snapshot (`.bos/sync-snapshot.json`)
28
+ 8. `bun install` + `bos types gen`
29
+
30
+ ### .templatekeep
31
+
32
+ Lists glob patterns for files that should be copied from template:
33
+ - Scaffold runtime files (bos.config.json, package.json, biome.json, rsbuild configs)
34
+ - UI structure (routes/__root.tsx, components/index.ts, providers, hooks, lib)
35
+ - API structure (contract.ts, index.ts, db/, drizzle.config.ts)
36
+ - Plugin template (`plugins/_template/**`)
37
+ - GitHub workflows (`.github/templates/**` → `.github/`)
38
+
39
+ ### .templatesync-exclude
40
+
41
+ Files copied on init but **never overwritten** on sync:
42
+ - `ui/src/components/**` — user-owned after init
43
+ - `ui/src/styles.css` — user-owned
44
+ - `ui/src/routes/_layout/**` — user-owned routes
45
+ - `api/src/contract.ts`, `api/src/index.ts`, `api/src/db/schema.ts` — user-owned business logic
46
+ - Generated files (`*.gen.ts`) — always regenerated
47
+
48
+ ## bos sync
49
+
50
+ Pulls updates from the parent template:
51
+
52
+ ```bash
53
+ bos sync # Sync from extends reference
54
+ bos sync --force # Overwrite even locally modified files
55
+ bos sync --files # Also sync template files
56
+ ```
57
+
58
+ ### Snapshot-based conflict detection
59
+
60
+ Uses `.bos/sync-snapshot.json` to track which files came from the template and their hashes:
61
+
62
+ | Local state | Template changed? | Action |
63
+ |------------|-------------------|--------|
64
+ | No local file | Yes | Add |
65
+ | Matches snapshot | Yes | Update (safe) |
66
+ | Modified since snapshot | Yes | Skip (unless `--force`) |
67
+ | Matches template | No | Skip |
68
+
69
+ Framework-owned files (from `FRAMEWORK_OWNED_SYNC_FILES`) are always updated when changed.
70
+
71
+ ### What gets synced
72
+
73
+ From parent template → local:
74
+ - `app.*.production` — Zephyr URLs
75
+ - `shared` — dependency versions
76
+ - Framework-owned files (rsbuild configs, routers, etc.)
77
+
78
+ What stays local:
79
+ - `account`, `testnet` — your NEAR accounts
80
+ - `app.*.development` — local dev paths
81
+
82
+ ### extends handling
83
+
84
+ Sync reads the `extends` field (string or object form) to find the parent. For object extends, uses the `production` URL to locate the template source.
85
+
86
+ ## bos upgrade
87
+
88
+ Bumps `everything-dev` and `every-plugin` across all workspaces:
89
+
90
+ ```bash
91
+ bos upgrade # Check for new versions, update, then sync
92
+ bos upgrade --dry-run # Preview without making changes
93
+ ```
94
+
95
+ ### Flow
96
+ 1. Check npm registry for latest versions of `everything-dev` and `every-plugin`
97
+ 2. Update root `package.json` workspaces.catalog
98
+ 3. Update all workspace `package.json` to use `catalog:` references
99
+ 4. `bun install` + `bos types gen`
100
+ 5. Run `bos sync` to pull template updates matching new version
101
+ 6. Rewrite legacy UI imports (e.g., `from "@/auth"` → `from "@/app"`)
102
+ 7. Remove obsolete files
103
+
104
+ ### Package ref strategy
105
+
106
+ Workspace packages use `catalog:` refs so a single version bump in root catalog propagates everywhere. Skips `workspace:*` and `file:` refs.
107
+
108
+ ## bos publish
109
+
110
+ ```bash
111
+ bos publish # Publish config to FastKV
112
+ bos publish --deploy # Build, deploy to CDN, then publish
113
+ ```
114
+
115
+ On `--deploy`:
116
+ 1. Build all workspace targets
117
+ 2. Deploy to Zephyr CDN → auto-updates `bos.config.json` with production URLs + integrity
118
+ 3. Re-read config to pick up deploy updates
119
+ 4. Publish full config to FastKV registry
120
+
121
+ **This is the only time `bos dev`-style writes touch `bos.config.json`** — it's the snapshot moment.
122
+
123
+ ## Canonical Ordering
124
+
125
+ All writes to `bos.config.json` enforce `BOS_CONFIG_ORDER`:
126
+ `extends` → `account` → `domain` → `testnet` → `staging` → `repository` → `app` → `plugins` → `shared`
127
+
128
+ Unknown keys go after known keys.
@@ -114,6 +114,36 @@ All runtime config lives in `bos.config.json`. Key sections:
114
114
  - `plugins.{key}` — Plugin configs with variables, secrets, routes
115
115
  - `shared.ui`, `shared.api` — Module Federation shared dependency versions
116
116
 
117
+ ### extends
118
+
119
+ Config can inherit from a parent via `extends`:
120
+ ```json
121
+ { "extends": "bos://dev.everything.near/everything.dev" }
122
+ ```
123
+
124
+ Or per-environment:
125
+ ```json
126
+ {
127
+ "extends": {
128
+ "development": "bos://dev.everything.near/everything.dev",
129
+ "production": "bos://dev.everything.near/everything.dev",
130
+ "staging": "bos://staging.everything.near/everything.dev"
131
+ }
132
+ }
133
+ ```
134
+
135
+ Deep merge: child overrides parent. Plugins are deep-merged (set to `null` to remove). `secrets` arrays are unioned. See the `extends-config` skill for full details.
136
+
137
+ ### What bos dev writes vs bos publish writes
138
+
139
+ | Mode | Writes to | File |
140
+ |------|-----------|------|
141
+ | `bos dev` | `.bos/bos.resolved-config.json` | Full merged config (gitignored) |
142
+ | `bos build` | `.bos/bos.resolved-config.json` | Full merged config |
143
+ | `bos publish --deploy` | `bos.config.json` | Snapshot with pinned production URLs |
144
+ | `bos plugin publish` | `bos.config.json` | Records production URL + integrity |
145
+ | `bos sync` | `bos.config.json` | Merges template updates |
146
+
117
147
  ## Troubleshooting
118
148
 
119
149
  ```bash
package/src/app.ts CHANGED
@@ -51,6 +51,9 @@ export function detectLocalPackages(
51
51
  if (pluginConfig.localPath && existsSync(join(pluginConfig.localPath, "package.json"))) {
52
52
  packages.push(`plugin:${pluginId}`);
53
53
  }
54
+ if (pluginConfig.ui?.localPath && existsSync(join(pluginConfig.ui.localPath, "package.json"))) {
55
+ packages.push(`plugin-ui:${pluginId}`);
56
+ }
54
57
  }
55
58
 
56
59
  const authLocalPath =
@@ -169,13 +172,20 @@ export async function prepareDevelopmentRuntimeConfig(
169
172
  let pluginBasePort = DEFAULT_PLUGIN_PORT_START;
170
173
 
171
174
  for (const [pluginId, plugin] of entries) {
172
- if (plugin.source !== "local" || !plugin.localPath) {
173
- continue;
175
+ if (plugin.source === "local" && plugin.localPath) {
176
+ const pluginPort = await pickAvailablePort(plugin.port ?? pluginBasePort, usedPorts);
177
+ next.plugins[pluginId] = withLocalRuntimeUrl(plugin, pluginPort);
178
+ pluginBasePort = pluginPort + 1;
174
179
  }
175
180
 
176
- const pluginPort = await pickAvailablePort(plugin.port ?? pluginBasePort, usedPorts);
177
- next.plugins[pluginId] = withLocalRuntimeUrl(plugin, pluginPort);
178
- pluginBasePort = pluginPort + 1;
181
+ if (plugin.ui?.source === "local" && plugin.ui.localPath) {
182
+ const uiPort = await pickAvailablePort(plugin.ui.port ?? pluginBasePort, usedPorts);
183
+ next.plugins[pluginId] = {
184
+ ...next.plugins[pluginId]!,
185
+ ui: withLocalRuntimeUrl(plugin.ui, uiPort),
186
+ };
187
+ pluginBasePort = uiPort + 1;
188
+ }
179
189
  }
180
190
  }
181
191