@valbuild/cli 0.120.4 → 0.122.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# @valbuild/cli
|
|
2
2
|
|
|
3
|
+
## 0.122.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`be32261`](https://github.com/valbuild/val/commit/be32261af19db8018bc37b180d903416018c0b79), [`5d14612`](https://github.com/valbuild/val/commit/5d14612f612d657a37338136188f2b3c02b28fe7), [`da6794f`](https://github.com/valbuild/val/commit/da6794f3dbd77d49ccfe780b359bab1689ee1b11)]:
|
|
8
|
+
- @valbuild/server@0.122.0
|
|
9
|
+
- @valbuild/shared@0.122.0
|
|
10
|
+
- @valbuild/language-server@0.122.0
|
|
11
|
+
|
|
12
|
+
## 0.121.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- [#605](https://github.com/valbuild/val/pull/605) [`6794d29`](https://github.com/valbuild/val/commit/6794d2980bc81284ab7f2cc667f01cc21c9e3a79) Thanks [@freekh](https://github.com/freekh)! - `s.settings()`: the project's settings, as content.
|
|
17
|
+
|
|
18
|
+
A settings module is one per project, at the root of the content tree:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// settings.val.ts
|
|
22
|
+
export default c.define("/settings.val.ts", s.settings(), {});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Register it in `val.modules.ts` like any other module, and it shows up in the
|
|
26
|
+
Studio under the cog at the foot of the left rail. Everything in it is content:
|
|
27
|
+
it is edited as a draft, it appears in the publish diff, and it is the same for
|
|
28
|
+
everyone working on the project.
|
|
29
|
+
|
|
30
|
+
Every key is optional, at every level, so `{}` is a complete settings module —
|
|
31
|
+
and stays one as sections are added. What it holds today is the assistant:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
export default c.define("/settings.val.ts", s.settings(), {
|
|
35
|
+
assistant: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
context: "A CMS for developers, run by a team of four in Oslo.",
|
|
38
|
+
tone: "Plain and direct. British English, sentence case in headings.",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`context` is background the assistant would otherwise guess at; `tone` is how it
|
|
44
|
+
should write when it writes content. Both are sent with every message it makes.
|
|
45
|
+
|
|
46
|
+
`enabled` decides whether editors have an assistant, and it has **three** states
|
|
47
|
+
rather than two:
|
|
48
|
+
|
|
49
|
+
- `true` — they do.
|
|
50
|
+
- `false` — they do not, and every trace of it goes: no button in the top bar,
|
|
51
|
+
no row in the quick actions, no panel, nothing sent.
|
|
52
|
+
- unset — nobody has decided. The assistant is still **shown**, and asks to be
|
|
53
|
+
turned on before it is used. Hiding an assistant nobody has decided about
|
|
54
|
+
means nobody discovers it; quietly enabling one means a project starts sending
|
|
55
|
+
its content to a model because it did not know to say no.
|
|
56
|
+
|
|
57
|
+
A project with no settings module at all has an assistant, as before: there is
|
|
58
|
+
nowhere to record a decision, and nowhere for the prompt to write the answer.
|
|
59
|
+
|
|
60
|
+
**Breaking: `ai.chat` is gone from `val.config.ts`.** Whether the assistant is
|
|
61
|
+
available is a decision about the project's content, made by the people who edit
|
|
62
|
+
it, so it moved to settings — turning the chat on used to take a developer, a
|
|
63
|
+
deploy and a code review of a boolean. Remove the whole block:
|
|
64
|
+
|
|
65
|
+
```diff
|
|
66
|
+
const { s, c, val, config } = initVal({
|
|
67
|
+
- ai: {
|
|
68
|
+
- chat: {
|
|
69
|
+
- experimental: { enable: true },
|
|
70
|
+
- suggestions: ["Summarize", "Fix typos at this page"],
|
|
71
|
+
- title: "Ask me anything",
|
|
72
|
+
- description: "Val can answer questions about the content.",
|
|
73
|
+
- },
|
|
74
|
+
- },
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`experimental.enable` becomes `assistant.enabled` in the settings module.
|
|
79
|
+
`suggestions`, `title` and `description` are removed with nothing replacing
|
|
80
|
+
them: the assistant now opens with its own copy. A project that had the chat
|
|
81
|
+
enabled and wants it to stay on for everyone should write
|
|
82
|
+
`assistant: { enabled: true }` — otherwise editors are offered it and asked.
|
|
83
|
+
|
|
84
|
+
`ai.commitMessages` stays in `val.config.ts`, and is unaffected.
|
|
85
|
+
|
|
86
|
+
Two settings modules, or one in a subdirectory, is a module error: the dev
|
|
87
|
+
server refuses to serve sources, `npx val validate` reports it against the file,
|
|
88
|
+
and the Studio says so rather than picking one.
|
|
89
|
+
|
|
90
|
+
### Patch Changes
|
|
91
|
+
|
|
92
|
+
- Updated dependencies [[`2bcc6fd`](https://github.com/valbuild/val/commit/2bcc6fdff8d668123e07e3c5e81ac6fa1436e47b), [`2bcbee1`](https://github.com/valbuild/val/commit/2bcbee1be682c2bbd5b7bc7d152ddd4204162fd2), [`6794d29`](https://github.com/valbuild/val/commit/6794d2980bc81284ab7f2cc667f01cc21c9e3a79)]:
|
|
93
|
+
- @valbuild/shared@0.121.0
|
|
94
|
+
- @valbuild/server@0.121.0
|
|
95
|
+
- @valbuild/core@0.121.0
|
|
96
|
+
- @valbuild/language-server@0.121.0
|
|
97
|
+
|
|
3
98
|
## 0.120.4
|
|
4
99
|
|
|
5
100
|
### Patch Changes
|
|
@@ -145,6 +145,28 @@ async function* runValidation({
|
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
147
|
const start = Date.now();
|
|
148
|
+
// How this module is DECLARED, before what is in it: a module error is not
|
|
149
|
+
// something content can fix (a misplaced settings module is otherwise
|
|
150
|
+
// perfectly valid content), and validating on top of it would report the
|
|
151
|
+
// consequences instead of the cause.
|
|
152
|
+
const moduleErrors = service.getModuleErrors().filter(moduleError => moduleError.path === moduleFilePath);
|
|
153
|
+
if (moduleErrors.length > 0) {
|
|
154
|
+
for (const moduleError of moduleErrors) {
|
|
155
|
+
errors += 1;
|
|
156
|
+
yield {
|
|
157
|
+
type: "fatal-error",
|
|
158
|
+
file: moduleFilePath,
|
|
159
|
+
message: moduleError.message
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
yield {
|
|
163
|
+
type: "file-error-count",
|
|
164
|
+
file: moduleFilePath,
|
|
165
|
+
errorCount: moduleErrors.length,
|
|
166
|
+
durationMs: Date.now() - start
|
|
167
|
+
};
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
148
170
|
const valModule = await service.get(moduleFilePath, "", {
|
|
149
171
|
validate: true
|
|
150
172
|
});
|
|
@@ -145,6 +145,28 @@ async function* runValidation({
|
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
147
|
const start = Date.now();
|
|
148
|
+
// How this module is DECLARED, before what is in it: a module error is not
|
|
149
|
+
// something content can fix (a misplaced settings module is otherwise
|
|
150
|
+
// perfectly valid content), and validating on top of it would report the
|
|
151
|
+
// consequences instead of the cause.
|
|
152
|
+
const moduleErrors = service.getModuleErrors().filter(moduleError => moduleError.path === moduleFilePath);
|
|
153
|
+
if (moduleErrors.length > 0) {
|
|
154
|
+
for (const moduleError of moduleErrors) {
|
|
155
|
+
errors += 1;
|
|
156
|
+
yield {
|
|
157
|
+
type: "fatal-error",
|
|
158
|
+
file: moduleFilePath,
|
|
159
|
+
message: moduleError.message
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
yield {
|
|
163
|
+
type: "file-error-count",
|
|
164
|
+
file: moduleFilePath,
|
|
165
|
+
errorCount: moduleErrors.length,
|
|
166
|
+
durationMs: Date.now() - start
|
|
167
|
+
};
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
148
170
|
const valModule = await service.get(moduleFilePath, "", {
|
|
149
171
|
validate: true
|
|
150
172
|
});
|
|
@@ -113,6 +113,28 @@ async function* runValidation({
|
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
const start = Date.now();
|
|
116
|
+
// How this module is DECLARED, before what is in it: a module error is not
|
|
117
|
+
// something content can fix (a misplaced settings module is otherwise
|
|
118
|
+
// perfectly valid content), and validating on top of it would report the
|
|
119
|
+
// consequences instead of the cause.
|
|
120
|
+
const moduleErrors = service.getModuleErrors().filter(moduleError => moduleError.path === moduleFilePath);
|
|
121
|
+
if (moduleErrors.length > 0) {
|
|
122
|
+
for (const moduleError of moduleErrors) {
|
|
123
|
+
errors += 1;
|
|
124
|
+
yield {
|
|
125
|
+
type: "fatal-error",
|
|
126
|
+
file: moduleFilePath,
|
|
127
|
+
message: moduleError.message
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
yield {
|
|
131
|
+
type: "file-error-count",
|
|
132
|
+
file: moduleFilePath,
|
|
133
|
+
errorCount: moduleErrors.length,
|
|
134
|
+
durationMs: Date.now() - start
|
|
135
|
+
};
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
116
138
|
const valModule = await service.get(moduleFilePath, "", {
|
|
117
139
|
validate: true
|
|
118
140
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valbuild/cli",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.122.0",
|
|
5
5
|
"description": "Val CLI tools",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"jszip": "^3.10.1",
|
|
25
25
|
"meow": "^9.0.0",
|
|
26
26
|
"picocolors": "^1.1.1",
|
|
27
|
-
"@valbuild/core": "0.
|
|
28
|
-
"@valbuild/language-server": "0.120.4",
|
|
27
|
+
"@valbuild/core": "0.121.0",
|
|
29
28
|
"@valbuild/eslint-plugin": "0.108.0",
|
|
30
|
-
"@valbuild/
|
|
31
|
-
"@valbuild/server": "0.
|
|
29
|
+
"@valbuild/language-server": "0.122.0",
|
|
30
|
+
"@valbuild/server": "0.122.0",
|
|
31
|
+
"@valbuild/shared": "0.122.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"prettier": "*",
|
|
@@ -878,4 +878,105 @@ describe("runValidation", () => {
|
|
|
878
878
|
expect(events.at(-1)).not.toEqual({ type: "summary-success" });
|
|
879
879
|
});
|
|
880
880
|
});
|
|
881
|
+
|
|
882
|
+
describe("settings modules", () => {
|
|
883
|
+
/**
|
|
884
|
+
* Writes a settings module into the fixture and registers it.
|
|
885
|
+
*
|
|
886
|
+
* Written by the test rather than kept as a fixture file: "one settings
|
|
887
|
+
* module, at the root" is a rule about the whole project, so a permanent
|
|
888
|
+
* broken fixture would be a rule the fixture project is always breaking.
|
|
889
|
+
*/
|
|
890
|
+
const addSettingsModule = (moduleFilePath: string) => {
|
|
891
|
+
const relative = moduleFilePath.slice(1);
|
|
892
|
+
const upToRoot = "../".repeat(relative.split("/").length - 1) || "./";
|
|
893
|
+
fs.mkdirSync(path.dirname(path.join(tmpDir, relative)), {
|
|
894
|
+
recursive: true,
|
|
895
|
+
});
|
|
896
|
+
fs.writeFileSync(
|
|
897
|
+
path.join(tmpDir, relative),
|
|
898
|
+
[
|
|
899
|
+
`import { s, c } from "${upToRoot}val.config";`,
|
|
900
|
+
`export default c.define("${moduleFilePath}", s.settings(), {});`,
|
|
901
|
+
"",
|
|
902
|
+
].join("\n"),
|
|
903
|
+
);
|
|
904
|
+
const valModulesPath = path.join(tmpDir, "val.modules.ts");
|
|
905
|
+
const registration = ` { def: () => import("./${relative.replace(
|
|
906
|
+
/\.ts$/,
|
|
907
|
+
"",
|
|
908
|
+
)}") },\n]);`;
|
|
909
|
+
fs.writeFileSync(
|
|
910
|
+
valModulesPath,
|
|
911
|
+
fs
|
|
912
|
+
.readFileSync(valModulesPath, "utf8")
|
|
913
|
+
.replace(/\]\);\s*$/, registration),
|
|
914
|
+
);
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
const runOn = async (valFiles: string[]) => {
|
|
918
|
+
const events: ValidationEvent[] = [];
|
|
919
|
+
for await (const event of runValidation({
|
|
920
|
+
root: tmpDir,
|
|
921
|
+
fix: false,
|
|
922
|
+
valFiles,
|
|
923
|
+
project: undefined,
|
|
924
|
+
remote: mockRemote,
|
|
925
|
+
fs: createDefaultValFSHost(),
|
|
926
|
+
})) {
|
|
927
|
+
events.push(event);
|
|
928
|
+
}
|
|
929
|
+
return events;
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
test("a settings module at the root is valid", async () => {
|
|
933
|
+
addSettingsModule("/settings.val.ts");
|
|
934
|
+
|
|
935
|
+
expect((await runOn(["settings.val.ts"])).at(-1)).toEqual({
|
|
936
|
+
type: "summary-success",
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
test("rejects a settings module in a subdirectory", async () => {
|
|
941
|
+
addSettingsModule("/content/settings.val.ts");
|
|
942
|
+
|
|
943
|
+
const events = await runOn(["content/settings.val.ts"]);
|
|
944
|
+
|
|
945
|
+
expect(events).toEqual(
|
|
946
|
+
expect.arrayContaining([
|
|
947
|
+
expect.objectContaining({
|
|
948
|
+
type: "fatal-error",
|
|
949
|
+
message: expect.stringContaining(
|
|
950
|
+
"Settings must be defined at the root of the content tree",
|
|
951
|
+
),
|
|
952
|
+
}),
|
|
953
|
+
]),
|
|
954
|
+
);
|
|
955
|
+
expect(events.at(-1)).not.toEqual({ type: "summary-success" });
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
test("rejects a second settings module, naming both", async () => {
|
|
959
|
+
addSettingsModule("/settings.val.ts");
|
|
960
|
+
addSettingsModule("/config.val.ts");
|
|
961
|
+
|
|
962
|
+
const events = await runOn(["settings.val.ts", "config.val.ts"]);
|
|
963
|
+
|
|
964
|
+
expect(events).toEqual(
|
|
965
|
+
expect.arrayContaining([
|
|
966
|
+
expect.objectContaining({
|
|
967
|
+
type: "fatal-error",
|
|
968
|
+
file: "/settings.val.ts",
|
|
969
|
+
message: expect.stringContaining(
|
|
970
|
+
"A project can only define settings once",
|
|
971
|
+
),
|
|
972
|
+
}),
|
|
973
|
+
expect.objectContaining({
|
|
974
|
+
type: "fatal-error",
|
|
975
|
+
file: "/config.val.ts",
|
|
976
|
+
}),
|
|
977
|
+
]),
|
|
978
|
+
);
|
|
979
|
+
expect(events.at(-1)).not.toEqual({ type: "summary-success" });
|
|
980
|
+
});
|
|
981
|
+
});
|
|
881
982
|
});
|
package/src/runValidation.ts
CHANGED
|
@@ -134,6 +134,30 @@ export async function* runValidation({
|
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
136
|
const start = Date.now();
|
|
137
|
+
// How this module is DECLARED, before what is in it: a module error is not
|
|
138
|
+
// something content can fix (a misplaced settings module is otherwise
|
|
139
|
+
// perfectly valid content), and validating on top of it would report the
|
|
140
|
+
// consequences instead of the cause.
|
|
141
|
+
const moduleErrors = service
|
|
142
|
+
.getModuleErrors()
|
|
143
|
+
.filter((moduleError) => moduleError.path === moduleFilePath);
|
|
144
|
+
if (moduleErrors.length > 0) {
|
|
145
|
+
for (const moduleError of moduleErrors) {
|
|
146
|
+
errors += 1;
|
|
147
|
+
yield {
|
|
148
|
+
type: "fatal-error",
|
|
149
|
+
file: moduleFilePath,
|
|
150
|
+
message: moduleError.message,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
yield {
|
|
154
|
+
type: "file-error-count",
|
|
155
|
+
file: moduleFilePath,
|
|
156
|
+
errorCount: moduleErrors.length,
|
|
157
|
+
durationMs: Date.now() - start,
|
|
158
|
+
};
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
137
161
|
const valModule = await service.get(moduleFilePath, "" as ModulePath, {
|
|
138
162
|
validate: true,
|
|
139
163
|
});
|