silvermoon 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -236
- package/README.zh-CN.md +114 -199
- package/assets/silvermoon.svg +176 -175
- package/bin/silvermoon.js +0 -0
- package/docs/assets/silvermoon-avatar.svg +395 -0
- package/docs/core-concepts.md +69 -0
- package/docs/getting-started.md +115 -0
- package/docs/maintaining.md +56 -0
- package/docs/npm-package-releases.md +139 -0
- package/docs/operations.md +110 -0
- package/docs/reference.md +138 -0
- package/docs/repository-tasks.md +61 -0
- package/package.json +69 -65
- package/schema/v1/config.schema.json +27 -24
- package/schema/v1/definitions.schema.json +30 -25
- package/schema/v1/idea-status.schema.json +44 -41
- package/schema/v1/user-config.schema.json +19 -0
- package/skills/silvermoon/SKILL.md +216 -209
- package/skills/silvermoon/references/adoption.md +122 -122
- package/src/adoption.js +313 -313
- package/src/cli.js +200 -196
- package/src/config.js +193 -176
- package/src/create-idea.js +272 -235
- package/src/git.js +284 -284
- package/src/idea-layout.js +439 -439
- package/src/idea-templates.js +109 -109
- package/src/ideas.js +140 -131
- package/src/index.js +260 -260
- package/src/language.js +29 -0
- package/src/layout.js +21 -21
- package/src/repository.js +87 -87
- package/src/user-config.js +135 -0
- package/src/whatsnext.js +453 -378
- package/src/yaml.js +66 -66
package/src/config.js
CHANGED
|
@@ -1,176 +1,193 @@
|
|
|
1
|
-
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { CONFIG_PATH, METADATA_ROOT } from "./layout.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
missing
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { CONFIG_PATH, METADATA_ROOT } from "./layout.js";
|
|
5
|
+
import { isCanonicalLanguageTag } from "./language.js";
|
|
6
|
+
import { validBranchName, validRepository } from "./repository.js";
|
|
7
|
+
import { parseStrictYaml, stringifyCanonicalYaml } from "./yaml.js";
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_CONFIG_NAME = CONFIG_PATH;
|
|
10
|
+
|
|
11
|
+
const CONFIG_KEYS = [
|
|
12
|
+
"version",
|
|
13
|
+
"primaryRepository",
|
|
14
|
+
"primaryBranch",
|
|
15
|
+
"preferredLanguage",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
function configDiagnostic(code, path, message, remediation) {
|
|
19
|
+
return { code, level: "error", path, message, remediation };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function regularRepositoryFile(root) {
|
|
23
|
+
const metadataRoot = resolve(root, METADATA_ROOT);
|
|
24
|
+
const absolutePath = resolve(root, CONFIG_PATH);
|
|
25
|
+
try {
|
|
26
|
+
const directoryMetadata = await lstat(metadataRoot);
|
|
27
|
+
if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()) {
|
|
28
|
+
throw Object.assign(new Error(`${METADATA_ROOT} must be a regular directory`), {
|
|
29
|
+
code: "EINVAL",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const fileMetadata = await lstat(absolutePath);
|
|
33
|
+
if (!fileMetadata.isFile() || fileMetadata.isSymbolicLink()) {
|
|
34
|
+
throw Object.assign(new Error("Configuration must be a regular file"), {
|
|
35
|
+
code: "EINVAL",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return { absolutePath, source: await readFile(absolutePath, "utf8") };
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const missing = error.code === "ENOENT";
|
|
41
|
+
return {
|
|
42
|
+
absolutePath,
|
|
43
|
+
diagnostic: configDiagnostic(
|
|
44
|
+
missing ? "config.missing" : "config.invalid-file",
|
|
45
|
+
CONFIG_PATH,
|
|
46
|
+
missing
|
|
47
|
+
? `Missing Silvermoon configuration: ${CONFIG_PATH}`
|
|
48
|
+
: `Cannot read Silvermoon configuration: ${error.message}`,
|
|
49
|
+
missing
|
|
50
|
+
? `Create ${CONFIG_PATH}.`
|
|
51
|
+
: `Replace ${METADATA_ROOT} and ${CONFIG_PATH} with repository-owned regular paths.`,
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function validPrimaryBranch(_root, value) {
|
|
58
|
+
return validBranchName(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function serializeConfig(config) {
|
|
62
|
+
const canonical = {
|
|
63
|
+
version: config.version,
|
|
64
|
+
primaryRepository: config.primaryRepository,
|
|
65
|
+
primaryBranch: config.primaryBranch,
|
|
66
|
+
};
|
|
67
|
+
if (config.preferredLanguage !== undefined) {
|
|
68
|
+
canonical.preferredLanguage = config.preferredLanguage;
|
|
69
|
+
}
|
|
70
|
+
return stringifyCanonicalYaml(canonical);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function loadConfig({ root }) {
|
|
74
|
+
const loaded = await regularRepositoryFile(root);
|
|
75
|
+
if (loaded.diagnostic) {
|
|
76
|
+
return {
|
|
77
|
+
config: null,
|
|
78
|
+
configPath: loaded.absolutePath,
|
|
79
|
+
diagnostics: [loaded.diagnostic],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const normalizedSource = loaded.source.replaceAll("\r\n", "\n");
|
|
84
|
+
let value;
|
|
85
|
+
try {
|
|
86
|
+
value = parseStrictYaml(normalizedSource);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
config: null,
|
|
90
|
+
configPath: loaded.absolutePath,
|
|
91
|
+
diagnostics: [configDiagnostic(
|
|
92
|
+
"config.invalid-yaml",
|
|
93
|
+
CONFIG_PATH,
|
|
94
|
+
`Cannot parse Silvermoon configuration: ${error.message}`,
|
|
95
|
+
`Use the strict YAML contract in ${CONFIG_PATH}.`,
|
|
96
|
+
)],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
101
|
+
return {
|
|
102
|
+
config: null,
|
|
103
|
+
configPath: loaded.absolutePath,
|
|
104
|
+
diagnostics: [configDiagnostic(
|
|
105
|
+
"config.invalid-type",
|
|
106
|
+
CONFIG_PATH,
|
|
107
|
+
"The Silvermoon configuration must be a YAML mapping.",
|
|
108
|
+
`Replace ${CONFIG_PATH} with the documented mapping.`,
|
|
109
|
+
)],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const diagnostics = [];
|
|
114
|
+
for (const key of Object.keys(value).sort()) {
|
|
115
|
+
if (!CONFIG_KEYS.includes(key)) {
|
|
116
|
+
diagnostics.push(configDiagnostic(
|
|
117
|
+
"config.unknown-key",
|
|
118
|
+
`${CONFIG_PATH}#${key}`,
|
|
119
|
+
`Unknown Silvermoon configuration key: ${key}`,
|
|
120
|
+
`Remove ${key}.`,
|
|
121
|
+
));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const [key, code] of [
|
|
125
|
+
["version", "config.missing-version"],
|
|
126
|
+
["primaryRepository", "config.missing-primary-repository"],
|
|
127
|
+
["primaryBranch", "config.missing-primary-branch"],
|
|
128
|
+
]) {
|
|
129
|
+
if (!Object.hasOwn(value, key)) {
|
|
130
|
+
diagnostics.push(configDiagnostic(
|
|
131
|
+
code,
|
|
132
|
+
`${CONFIG_PATH}#${key}`,
|
|
133
|
+
`Missing required key: ${key}`,
|
|
134
|
+
`Add ${key} to ${CONFIG_PATH}.`,
|
|
135
|
+
));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (Object.hasOwn(value, "version") && value.version !== 1) {
|
|
139
|
+
diagnostics.push(configDiagnostic(
|
|
140
|
+
"config.unsupported-version",
|
|
141
|
+
`${CONFIG_PATH}#version`,
|
|
142
|
+
`Unsupported Silvermoon version: ${String(value.version)}`,
|
|
143
|
+
"Use version: 1.",
|
|
144
|
+
));
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
Object.hasOwn(value, "primaryRepository") &&
|
|
148
|
+
!validRepository(value.primaryRepository)
|
|
149
|
+
) {
|
|
150
|
+
diagnostics.push(configDiagnostic(
|
|
151
|
+
"config.invalid-primary-repository",
|
|
152
|
+
`${CONFIG_PATH}#primaryRepository`,
|
|
153
|
+
"primaryRepository must be a canonical credential-free HTTPS repository URL.",
|
|
154
|
+
"Use a canonical HTTPS repository URL without credentials, query, fragment, or trailing slash.",
|
|
155
|
+
));
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
Object.hasOwn(value, "primaryBranch") &&
|
|
159
|
+
!validPrimaryBranch(root, value.primaryBranch)
|
|
160
|
+
) {
|
|
161
|
+
diagnostics.push(configDiagnostic(
|
|
162
|
+
"config.invalid-primary-branch",
|
|
163
|
+
`${CONFIG_PATH}#primaryBranch`,
|
|
164
|
+
"primaryBranch must be a valid short Git branch name.",
|
|
165
|
+
"Use a branch such as main without refs/ or remote prefixes.",
|
|
166
|
+
));
|
|
167
|
+
}
|
|
168
|
+
if (
|
|
169
|
+
Object.hasOwn(value, "preferredLanguage") &&
|
|
170
|
+
!isCanonicalLanguageTag(value.preferredLanguage)
|
|
171
|
+
) {
|
|
172
|
+
diagnostics.push(configDiagnostic(
|
|
173
|
+
"config.invalid-preferred-language",
|
|
174
|
+
`${CONFIG_PATH}#preferredLanguage`,
|
|
175
|
+
"preferredLanguage must be a canonical BCP 47 language tag.",
|
|
176
|
+
"Use a canonical tag such as en or zh-CN.",
|
|
177
|
+
));
|
|
178
|
+
}
|
|
179
|
+
if (diagnostics.length === 0 && serializeConfig(value) !== normalizedSource) {
|
|
180
|
+
diagnostics.push(configDiagnostic(
|
|
181
|
+
"config.noncanonical",
|
|
182
|
+
CONFIG_PATH,
|
|
183
|
+
"The Silvermoon configuration is valid but not canonical.",
|
|
184
|
+
"Rewrite properties in version, primaryRepository, primaryBranch, preferredLanguage order with LF endings.",
|
|
185
|
+
));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
config: diagnostics.length === 0 ? value : null,
|
|
190
|
+
configPath: loaded.absolutePath,
|
|
191
|
+
diagnostics,
|
|
192
|
+
};
|
|
193
|
+
}
|