everything-dev 1.12.3 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +1 -1
- package/dist/app.cjs +24 -101
- package/dist/app.cjs.map +1 -1
- package/dist/app.mjs +25 -102
- package/dist/app.mjs.map +1 -1
- package/dist/cli/init.cjs +143 -66
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +1 -1
- package/dist/cli/init.d.cts.map +1 -1
- package/dist/cli/init.d.mts +1 -1
- package/dist/cli/init.d.mts.map +1 -1
- package/dist/cli/init.mjs +144 -67
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/prompts.cjs +3 -3
- package/dist/cli/prompts.cjs.map +1 -1
- package/dist/cli/prompts.mjs +3 -3
- package/dist/cli/prompts.mjs.map +1 -1
- package/dist/cli/sync.cjs +15 -56
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +15 -56
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/cli/upgrade.cjs +3 -1
- package/dist/cli/upgrade.cjs.map +1 -1
- package/dist/cli/upgrade.mjs +3 -1
- package/dist/cli/upgrade.mjs.map +1 -1
- package/dist/config.cjs +223 -81
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts +21 -5
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +21 -5
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +217 -83
- package/dist/config.mjs.map +1 -1
- package/dist/contract.d.cts +104 -8
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +104 -8
- package/dist/contract.d.mts.map +1 -1
- package/dist/host.cjs +34 -1
- package/dist/host.cjs.map +1 -1
- package/dist/host.d.cts.map +1 -1
- package/dist/host.d.mts.map +1 -1
- package/dist/host.mjs +34 -1
- package/dist/host.mjs.map +1 -1
- package/dist/index.cjs +17 -0
- package/dist/index.d.cts +5 -3
- package/dist/index.d.mts +5 -3
- package/dist/index.mjs +5 -3
- package/dist/merge.cjs +113 -0
- package/dist/merge.cjs.map +1 -0
- package/dist/merge.d.cts +7 -0
- package/dist/merge.d.cts.map +1 -0
- package/dist/merge.d.mts +7 -0
- package/dist/merge.d.mts.map +1 -0
- package/dist/merge.mjs +107 -0
- package/dist/merge.mjs.map +1 -0
- package/dist/plugin.cjs +117 -105
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +114 -8
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +114 -8
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +117 -105
- package/dist/plugin.mjs.map +1 -1
- package/dist/service-descriptor.cjs +21 -0
- package/dist/service-descriptor.cjs.map +1 -1
- package/dist/service-descriptor.d.cts +23 -1
- package/dist/service-descriptor.d.cts.map +1 -1
- package/dist/service-descriptor.d.mts +23 -1
- package/dist/service-descriptor.d.mts.map +1 -1
- package/dist/service-descriptor.mjs +21 -0
- package/dist/service-descriptor.mjs.map +1 -1
- package/dist/shared.cjs +24 -2
- package/dist/shared.cjs.map +1 -1
- package/dist/shared.d.cts +3 -0
- package/dist/shared.d.cts.map +1 -1
- package/dist/shared.d.mts +3 -0
- package/dist/shared.d.mts.map +1 -1
- package/dist/shared.mjs +25 -3
- package/dist/shared.mjs.map +1 -1
- package/dist/sidebar.cjs +124 -0
- package/dist/sidebar.cjs.map +1 -0
- package/dist/sidebar.d.cts +8 -0
- package/dist/sidebar.d.cts.map +1 -0
- package/dist/sidebar.d.mts +8 -0
- package/dist/sidebar.d.mts.map +1 -0
- package/dist/sidebar.mjs +122 -0
- package/dist/sidebar.mjs.map +1 -0
- package/dist/types.cjs +104 -10
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +256 -29
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +256 -29
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +100 -11
- package/dist/types.mjs.map +1 -1
- package/dist/utils/path-match.cjs +18 -0
- package/dist/utils/path-match.cjs.map +1 -0
- package/dist/utils/path-match.mjs +17 -0
- package/dist/utils/path-match.mjs.map +1 -0
- package/dist/utils/save-config.cjs +19 -0
- package/dist/utils/save-config.cjs.map +1 -0
- package/dist/utils/save-config.mjs +18 -0
- package/dist/utils/save-config.mjs.map +1 -0
- package/package.json +3 -2
- package/skills/dev-workflow/SKILL.md +8 -0
- package/skills/extends-config/SKILL.md +132 -0
- package/skills/init-upgrade/SKILL.md +128 -0
- package/skills/publish-sync/SKILL.md +30 -0
- package/src/app.ts +23 -118
- package/src/cli/init.ts +199 -100
- package/src/cli/prompts.ts +2 -2
- package/src/cli/sync.ts +27 -96
- package/src/cli/upgrade.ts +2 -0
- package/src/config.ts +356 -132
- package/src/host.ts +45 -0
- package/src/index.ts +1 -0
- package/src/merge.ts +198 -0
- package/src/plugin.ts +340 -318
- package/src/service-descriptor.ts +23 -0
- package/src/shared.ts +48 -5
- package/src/sidebar.ts +162 -0
- package/src/types.ts +134 -28
- package/src/utils/path-match.ts +16 -0
- package/src/utils/save-config.ts +20 -0
|
@@ -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.
|
|
3
|
+
"version": "1.13.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -154,11 +154,12 @@
|
|
|
154
154
|
"@orpc/server": "^1.13.4",
|
|
155
155
|
"@orpc/zod": "^1.13.4",
|
|
156
156
|
"chalk": "^5.6.2",
|
|
157
|
+
"defu": "^6.1.7",
|
|
157
158
|
"effect": "^3.21.0",
|
|
158
159
|
"every-plugin": "^2.5.8",
|
|
159
160
|
"glob": "^13.0.6",
|
|
160
161
|
"gradient-string": "^3.0.0",
|
|
161
|
-
"hono": "^4.
|
|
162
|
+
"hono": "^4.12.18",
|
|
162
163
|
"ink": "^6.8.0",
|
|
163
164
|
"tar": "^7.4.3"
|
|
164
165
|
},
|
|
@@ -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
|
@@ -2,13 +2,10 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { createConnection } from "node:net";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import {
|
|
5
|
+
buildRuntimeConfig as configBuildRuntimeConfig,
|
|
5
6
|
getProjectRoot,
|
|
6
|
-
isLocalDevelopmentTarget,
|
|
7
|
-
parsePort,
|
|
8
7
|
resolveLocalDevelopmentPath,
|
|
9
|
-
resolvePluginRuntimeName,
|
|
10
8
|
} from "./config";
|
|
11
|
-
import { getNetworkIdForAccount } from "./network";
|
|
12
9
|
import type { AppOrchestrator } from "./service-descriptor";
|
|
13
10
|
import type { BosConfig, RuntimeConfig, RuntimePluginConfig } from "./types";
|
|
14
11
|
|
|
@@ -54,6 +51,9 @@ export function detectLocalPackages(
|
|
|
54
51
|
if (pluginConfig.localPath && existsSync(join(pluginConfig.localPath, "package.json"))) {
|
|
55
52
|
packages.push(`plugin:${pluginId}`);
|
|
56
53
|
}
|
|
54
|
+
if (pluginConfig.ui?.localPath && existsSync(join(pluginConfig.ui.localPath, "package.json"))) {
|
|
55
|
+
packages.push(`plugin-ui:${pluginId}`);
|
|
56
|
+
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const authLocalPath =
|
|
@@ -78,116 +78,14 @@ export function buildRuntimeConfig(
|
|
|
78
78
|
plugins?: Record<string, RuntimePluginConfig>;
|
|
79
79
|
},
|
|
80
80
|
): RuntimeConfig {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
function resolveDevelopmentEntry(
|
|
88
|
-
entry: { development?: string; production?: string },
|
|
89
|
-
preferredSource: "local" | "remote",
|
|
90
|
-
): { source: "local" | "remote"; url: string; localPath?: string; port?: number } {
|
|
91
|
-
if (preferredSource === "remote") {
|
|
92
|
-
return { source: "remote", url: entry.production ?? "" };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const localPath = resolveLocalDevelopmentPath(entry.development, configDir);
|
|
96
|
-
if (localPath && existsSync(localPath)) {
|
|
97
|
-
return { source: "local", url: "", localPath };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const devUrl =
|
|
101
|
-
entry.development && !isLocalDevelopmentTarget(entry.development)
|
|
102
|
-
? entry.development.replace(/\/$/, "")
|
|
103
|
-
: null;
|
|
104
|
-
if (devUrl) {
|
|
105
|
-
return { source: "local", url: devUrl, port: parsePort(devUrl) };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return { source: "remote", url: entry.production ?? "" };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const hostEntry = resolveDevelopmentEntry(hostConfig, options.hostSource ?? "local");
|
|
112
|
-
const uiEntry = resolveDevelopmentEntry(uiConfig, options.uiSource ?? "local");
|
|
113
|
-
const apiEntry = resolveDevelopmentEntry(apiConfig, options.apiSource ?? "local");
|
|
114
|
-
const authEntry = authConfig
|
|
115
|
-
? resolveDevelopmentEntry(authConfig, options.authSource ?? "local")
|
|
116
|
-
: undefined;
|
|
117
|
-
|
|
118
|
-
const hostUrl = `http://localhost:${DEFAULT_HOST_PORT}`;
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
env: options.env ?? "development",
|
|
122
|
-
account: bosConfig.account,
|
|
123
|
-
domain: bosConfig.domain,
|
|
124
|
-
networkId: getNetworkIdForAccount(bosConfig.account),
|
|
125
|
-
host: {
|
|
126
|
-
name: "host",
|
|
127
|
-
url: hostUrl,
|
|
128
|
-
entry: `${hostUrl}/mf-manifest.json`,
|
|
129
|
-
localPath: hostEntry.localPath,
|
|
130
|
-
port: hostEntry.port ?? DEFAULT_HOST_PORT,
|
|
131
|
-
secrets: hostConfig.secrets,
|
|
132
|
-
integrity: hostEntry.source === "remote" ? hostConfig.integrity : undefined,
|
|
133
|
-
source: hostEntry.source,
|
|
134
|
-
remoteUrl: hostEntry.source === "remote" ? hostEntry.url : undefined,
|
|
135
|
-
},
|
|
136
|
-
shared: bosConfig.shared,
|
|
137
|
-
ui: uiConfig
|
|
138
|
-
? {
|
|
139
|
-
name: uiConfig.name,
|
|
140
|
-
url: uiEntry.url,
|
|
141
|
-
entry: uiEntry.url ? `${uiEntry.url}/mf-manifest.json` : "/mf-manifest.json",
|
|
142
|
-
localPath: uiEntry.localPath,
|
|
143
|
-
port: uiEntry.port,
|
|
144
|
-
ssrUrl: uiEntry.source === "remote" ? uiConfig.ssr : undefined,
|
|
145
|
-
ssrIntegrity: uiEntry.source === "remote" ? uiConfig.ssrIntegrity : undefined,
|
|
146
|
-
integrity: uiEntry.source === "remote" ? uiConfig.integrity : undefined,
|
|
147
|
-
source: uiEntry.source,
|
|
148
|
-
}
|
|
149
|
-
: {
|
|
150
|
-
name: "ui",
|
|
151
|
-
url: "",
|
|
152
|
-
entry: "/mf-manifest.json",
|
|
153
|
-
source: uiEntry.source,
|
|
154
|
-
},
|
|
155
|
-
api: apiConfig
|
|
156
|
-
? {
|
|
157
|
-
name: apiConfig.name,
|
|
158
|
-
url: apiEntry.url,
|
|
159
|
-
entry: apiEntry.url ? `${apiEntry.url}/mf-manifest.json` : "/mf-manifest.json",
|
|
160
|
-
localPath: apiEntry.localPath,
|
|
161
|
-
port: apiEntry.port,
|
|
162
|
-
source: apiEntry.source,
|
|
163
|
-
proxy: options.proxy ?? apiConfig.proxy,
|
|
164
|
-
variables: apiConfig.variables,
|
|
165
|
-
secrets: apiConfig.secrets,
|
|
166
|
-
integrity: apiEntry.source === "remote" ? apiConfig.integrity : undefined,
|
|
167
|
-
}
|
|
168
|
-
: {
|
|
169
|
-
name: "api",
|
|
170
|
-
url: "",
|
|
171
|
-
entry: "/mf-manifest.json",
|
|
172
|
-
source: apiEntry.source,
|
|
173
|
-
},
|
|
174
|
-
auth:
|
|
175
|
-
authEntry && authConfig
|
|
176
|
-
? {
|
|
177
|
-
name: resolvePluginRuntimeName(undefined, authEntry.localPath, authConfig.name),
|
|
178
|
-
url: authEntry.url,
|
|
179
|
-
entry: authEntry.url ? `${authEntry.url}/mf-manifest.json` : "/mf-manifest.json",
|
|
180
|
-
localPath: authEntry.localPath,
|
|
181
|
-
port: authEntry.port,
|
|
182
|
-
source: authEntry.source,
|
|
183
|
-
proxy: authConfig.proxy,
|
|
184
|
-
variables: authConfig.variables,
|
|
185
|
-
secrets: authConfig.secrets,
|
|
186
|
-
integrity: authEntry.source === "remote" ? authConfig.integrity : undefined,
|
|
187
|
-
}
|
|
188
|
-
: undefined,
|
|
81
|
+
return configBuildRuntimeConfig(bosConfig, getProjectRoot(), options.env ?? "development", {
|
|
82
|
+
hostSource: options.hostSource,
|
|
83
|
+
uiSource: options.uiSource,
|
|
84
|
+
apiSource: options.apiSource,
|
|
85
|
+
authSource: options.authSource,
|
|
86
|
+
proxy: options.proxy,
|
|
189
87
|
plugins: options.plugins,
|
|
190
|
-
};
|
|
88
|
+
});
|
|
191
89
|
}
|
|
192
90
|
|
|
193
91
|
function probeTcpOpen(port: number, timeoutMs = 250): Promise<boolean> {
|
|
@@ -274,13 +172,20 @@ export async function prepareDevelopmentRuntimeConfig(
|
|
|
274
172
|
let pluginBasePort = DEFAULT_PLUGIN_PORT_START;
|
|
275
173
|
|
|
276
174
|
for (const [pluginId, plugin] of entries) {
|
|
277
|
-
if (plugin.source
|
|
278
|
-
|
|
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;
|
|
279
179
|
}
|
|
280
180
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
}
|
|
284
189
|
}
|
|
285
190
|
}
|
|
286
191
|
|