Skip to content

Commit 67c7ab0

Browse files
committed
C# configs
1 parent 8518378 commit 67c7ab0

20 files changed

Lines changed: 425 additions & 41 deletions

File tree

.config/cspell/cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"npmjs",
1414
"packagejson",
1515
"pacote",
16+
"rcompare",
1617
"rsort",
1718
"syncpack"
1819
]

.config/prettier/.prettierrc.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
{
22
"printWidth": 120,
33
"plugins": ["prettier-plugin-packagejson", "@prettier/plugin-xml"],
4-
"xmlWhitespaceSensitivity": "ignore"
4+
"xmlWhitespaceSensitivity": "ignore",
5+
"overrides": [
6+
{
7+
"files": "app.manifest",
8+
"options": {
9+
"parser": "xml"
10+
}
11+
}
12+
]
513
}

setup/configs/base/devcontainer.ts

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import type { CSpellUserSettings } from "cspell-lib";
2+
import { spellCheckDocument } from "cspell-lib";
3+
import * as jsonc from "jsonc-parser";
14
import semver from "semver";
25

3-
import { fillTemplate } from "../../formatting.js";
6+
import { fillTemplate, stringify } from "../../formatting.js";
47
import { getNodeLtsVersions } from "../../versions/node.js";
58
import { getNpmPackageDistTags } from "../../versions/npm.js";
69
import { getOciArtifactMaxMajorVersion, getOciArtifactTags } from "../../versions/oci.js";
710
import { loadTemplates } from "../templates.js";
11+
import type { BaseDotConfigs } from "./dotConfig.js";
812

913
export interface BaseDevcontainerConfigs {
1014
".devcontainer/.env": string;
@@ -29,37 +33,64 @@ const getPnpmLatestDistTag = async () => {
2933

3034
export const createBaseDevcontainerConfigs = async (
3135
projectName: string,
32-
remoteUser = "dev",
36+
remoteUser: string,
3337
): Promise<BaseDevcontainerConfigs> => {
34-
const userInitRepoName = "ghcr.io/devcontainer-config/features/user-init";
35-
const dotConfigRepoName = "ghcr.io/devcontainer-config/features/dot-config";
36-
const userInitVersion = await getOciArtifactMaxMajorVersion(userInitRepoName);
37-
const dotConfigVersion = await getOciArtifactMaxMajorVersion(dotConfigRepoName);
38-
39-
const nodeDevcontainerRepoName = "mcr.microsoft.com/devcontainers/javascript-node";
40-
const nodeLtsVersions = await getNodeLtsVersions();
41-
const nodeDevcontainerTags = new Set(await getOciArtifactTags(nodeDevcontainerRepoName));
42-
const nodeVersion = nodeLtsVersions.find((v) => nodeDevcontainerTags.has(v.toString()))?.toString() ?? "latest";
43-
44-
const templates = await loadTemplates("base", [
38+
const templatePaths = [
4539
".devcontainer/.env",
4640
".devcontainer/devcontainer.json",
4741
".devcontainer/docker-compose.yml",
4842
".devcontainer/Dockerfile",
4943
".devcontainer/dot-config.json",
50-
] satisfies (keyof BaseDevcontainerConfigs)[]);
44+
] satisfies (keyof BaseDevcontainerConfigs)[];
45+
const templates = await loadTemplates("base", [
46+
...templatePaths,
47+
".config/cspell/cspell.json" satisfies keyof BaseDotConfigs,
48+
]);
49+
50+
const devContainerConfig = await (async () => {
51+
const devContainerConfigPath = ".devcontainer/devcontainer.json" satisfies keyof BaseDevcontainerConfigs;
52+
const devContainerConfig = jsonc.parse(
53+
fillTemplate(templates[devContainerConfigPath], { projectName, remoteUser }),
54+
) as {
55+
features: object;
56+
};
57+
58+
const userInitRepoName = "ghcr.io/devcontainer-config/features/user-init";
59+
const dotConfigRepoName = "ghcr.io/devcontainer-config/features/dot-config";
60+
const userInitVersion = await getOciArtifactMaxMajorVersion(userInitRepoName);
61+
const dotConfigVersion = await getOciArtifactMaxMajorVersion(dotConfigRepoName);
62+
devContainerConfig.features = {
63+
[`${userInitRepoName}:${userInitVersion}`]: {},
64+
[`${dotConfigRepoName}:${dotConfigVersion}`]: {},
65+
};
66+
67+
const content = stringify(devContainerConfig);
68+
const spellCheckResult = await spellCheckDocument(
69+
{ uri: devContainerConfigPath, text: content },
70+
{ noConfigSearch: true },
71+
jsonc.parse(templates[".config/cspell/cspell.json"]) as CSpellUserSettings,
72+
);
73+
return [
74+
`// spell-checker:ignore ${[...new Set(spellCheckResult.issues.map((issue) => issue.text))].join(" ")}`,
75+
content,
76+
].join("\n");
77+
})();
78+
79+
const dockerFile = await (async () => {
80+
const nodeDevcontainerRepoName = "mcr.microsoft.com/devcontainers/javascript-node";
81+
const nodeLtsVersions = await getNodeLtsVersions();
82+
const nodeDevcontainerTags = new Set(await getOciArtifactTags(nodeDevcontainerRepoName));
83+
const nodeVersion = nodeLtsVersions.find((v) => nodeDevcontainerTags.has(v.toString()))?.toString() ?? "latest";
84+
const pnpmVersion = await getPnpmLatestDistTag();
85+
86+
return fillTemplate(templates[".devcontainer/Dockerfile"], { nodeVersion, pnpmVersion });
87+
})();
5188

52-
const pnpmVersion = await getPnpmLatestDistTag();
5389
return {
5490
".devcontainer/.env": fillTemplate(templates[".devcontainer/.env"], { projectName, remoteUser }),
55-
".devcontainer/devcontainer.json": fillTemplate(templates[".devcontainer/devcontainer.json"], {
56-
projectName,
57-
remoteUser,
58-
})
59-
.replaceAll(userInitRepoName, `${userInitRepoName}:${userInitVersion}`)
60-
.replaceAll(dotConfigRepoName, `${dotConfigRepoName}:${dotConfigVersion}`),
91+
".devcontainer/devcontainer.json": devContainerConfig,
6192
".devcontainer/docker-compose.yml": templates[".devcontainer/docker-compose.yml"],
62-
".devcontainer/Dockerfile": fillTemplate(templates[".devcontainer/Dockerfile"], { nodeVersion, pnpmVersion }),
93+
".devcontainer/Dockerfile": dockerFile,
6394
".devcontainer/dot-config.json": fillTemplate(templates[".devcontainer/dot-config.json"], { remoteUser }),
6495
};
6596
};

setup/configs/base/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { createBaseWorkspaceConfigs } from "./workspace.js";
88
export type BaseConfigs = BaseDevcontainerConfigs & BaseDotConfigs & BaseWorkspaceConfigs;
99

1010
export const createBaseConfigs = async (projectName: string, remoteUser = "dev"): Promise<BaseConfigs> => {
11-
const devcontainerConfigs = await createBaseDevcontainerConfigs(projectName, remoteUser);
11+
const devContainerConfigs = await createBaseDevcontainerConfigs(projectName, remoteUser);
1212
const dotConfigs = await createBaseDotConfigs(projectName, remoteUser);
1313
const workspaceConfigs = await createBaseWorkspaceConfigs();
1414
return {
15-
...devcontainerConfigs,
15+
...devContainerConfigs,
1616
...dotConfigs,
1717
...workspaceConfigs,
1818
};

setup/configs/composer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export const createComposer = (config: Config): typeof defaultComposer => {
1111
};
1212

1313
export const mergeArrayComposer = createComposer({ mergeArrays: true });
14+
15+
export const propertiesComposer = (...text: string[]) => text.flatMap((t) => t.trimEnd().split("\n")).join("\n") + "\n";

setup/configs/csharp/automation.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { X2jOptions } from "fast-xml-parser";
2+
import { XMLBuilder, XMLParser } from "fast-xml-parser";
3+
4+
import { getDotNetCoreLatestLtsRelease } from "../../versions/dotnet.js";
5+
import { loadTemplates } from "../templates.js";
6+
7+
export interface CSharpAutomationConfigs {
8+
"Automation/Tasks/CSharpier.cs": string;
9+
"Automation/Tasks/CSpell.cs": string;
10+
"Automation/Tasks/DotNetFormat.cs": string;
11+
"Automation/Tasks/Prettier.cs": string;
12+
"Automation/Automation.csproj": string;
13+
"Automation/Context.cs": string;
14+
"Automation/Format.cs": string;
15+
"Automation/Lint.cs": string;
16+
"Automation/Program.cs": string;
17+
"Automation/Restore.cs": string;
18+
}
19+
20+
export const createCSharpAutomationConfigs = async (): Promise<CSharpAutomationConfigs> => {
21+
const templates = await loadTemplates("csharp", [
22+
"Automation/Tasks/CSharpier.cs",
23+
"Automation/Tasks/CSpell.cs",
24+
"Automation/Tasks/DotNetFormat.cs",
25+
"Automation/Tasks/Prettier.cs",
26+
"Automation/Automation.csproj",
27+
"Automation/Context.cs",
28+
"Automation/Format.cs",
29+
"Automation/Lint.cs",
30+
"Automation/Program.cs",
31+
"Automation/Restore.cs",
32+
] as (keyof CSharpAutomationConfigs)[]);
33+
34+
const csproj = await (async () => {
35+
const options = { preserveOrder: true, ignoreAttributes: false } satisfies X2jOptions;
36+
const content = new XMLParser(options).parse(templates["Automation/Automation.csproj"]) as [
37+
{
38+
Project: [{ PropertyGroup: [object, { TargetFramework: [{ "#text": string }] }] }, object];
39+
},
40+
];
41+
42+
const { "channel-version": dotnetVersion } = await getDotNetCoreLatestLtsRelease();
43+
content[0].Project[0].PropertyGroup[1].TargetFramework[0]["#text"] = `net${dotnetVersion}`;
44+
return new XMLBuilder(options).build(content) as string;
45+
})();
46+
47+
templates["Automation/Automation.csproj"] = csproj;
48+
return templates;
49+
};
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { CSpellUserSettings } from "cspell-lib";
2+
import { spellCheckDocument } from "cspell-lib";
3+
import { defaultComposer } from "default-composer";
4+
import * as jsonc from "jsonc-parser";
5+
6+
import { fillTemplate, stringify } from "../../formatting.js";
7+
import { getDotNetCoreLatestLtsRelease } from "../../versions/dotnet.js";
8+
import { getOciArtifactMaxMajorVersion } from "../../versions/oci.js";
9+
import type { BaseDevcontainerConfigs } from "../base/devcontainer.js";
10+
import type { BaseConfigs } from "../base/index.js";
11+
import { mergeArrayComposer, propertiesComposer } from "../composer.js";
12+
import { loadTemplates } from "../templates.js";
13+
14+
export interface CSharpDevcontainerConfigs {
15+
".devcontainer/.env": string;
16+
".devcontainer/devcontainer.json": string;
17+
".devcontainer/dot-config.json": string;
18+
}
19+
20+
export const createCSharpDevcontainerConfigs = async (
21+
baseConfig: BaseConfigs,
22+
remoteUser: string,
23+
): Promise<CSharpDevcontainerConfigs> => {
24+
const templatePaths = [
25+
".devcontainer/.env",
26+
".devcontainer/devcontainer.json",
27+
".devcontainer/dot-config.json",
28+
] satisfies (keyof CSharpDevcontainerConfigs)[];
29+
const templates = await loadTemplates("csharp", [
30+
...templatePaths,
31+
".config/cspell/cspell.json" satisfies keyof BaseConfigs,
32+
]);
33+
34+
const devContainerConfig = await (async () => {
35+
const devContainerConfigPath = ".devcontainer/devcontainer.json" satisfies keyof BaseDevcontainerConfigs;
36+
const devContainerConfig = jsonc.parse(templates[devContainerConfigPath]) as { features: object };
37+
38+
const dotnetFeatureRepoName = "ghcr.io/devcontainers/features/dotnet";
39+
const dotnetFeatureVersion = await getOciArtifactMaxMajorVersion(dotnetFeatureRepoName);
40+
const { "channel-version": dotnetVersion } = await getDotNetCoreLatestLtsRelease();
41+
devContainerConfig.features = {
42+
[`${dotnetFeatureRepoName}:${dotnetFeatureVersion}`]: { version: dotnetVersion },
43+
};
44+
45+
const mergedDevContainerConfig = mergeArrayComposer(
46+
jsonc.parse(baseConfig[devContainerConfigPath]) as object,
47+
devContainerConfig,
48+
);
49+
50+
const content = stringify(mergedDevContainerConfig);
51+
const spellCheckResult = await spellCheckDocument(
52+
{ uri: ".devcontainer/devcontainer.json", text: content },
53+
{ noConfigSearch: true },
54+
mergeArrayComposer(
55+
jsonc.parse(baseConfig[".config/cspell/cspell.json"]) as CSpellUserSettings,
56+
jsonc.parse(templates[".config/cspell/cspell.json"]) as CSpellUserSettings,
57+
),
58+
);
59+
return [
60+
`// spell-checker:ignore ${[...new Set(spellCheckResult.issues.map((issue) => issue.text))].join(" ")}`,
61+
content,
62+
].join("\n");
63+
})();
64+
65+
return {
66+
".devcontainer/.env": propertiesComposer(
67+
baseConfig[".devcontainer/.env"],
68+
fillTemplate(templates[".devcontainer/.env"], { remoteUser }),
69+
),
70+
".devcontainer/devcontainer.json": devContainerConfig,
71+
".devcontainer/dot-config.json": stringify(
72+
defaultComposer(
73+
jsonc.parse(baseConfig[".devcontainer/dot-config.json"]) as object,
74+
jsonc.parse(templates[".devcontainer/dot-config.json"]) as object,
75+
),
76+
),
77+
};
78+
};

setup/configs/csharp/dotConfig.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { CSpellUserSettings } from "cspell-lib";
2+
import type { X2jOptions } from "fast-xml-parser";
3+
import { XMLBuilder, XMLParser } from "fast-xml-parser";
4+
import * as jsonc from "jsonc-parser";
5+
6+
import { stringify } from "../../formatting.js";
7+
import { getNugetPackageLatestVersion } from "../../versions/nuget.js";
8+
import type { BaseConfigs } from "../base/index.js";
9+
import { mergeArrayComposer } from "../composer.js";
10+
import { loadTemplates } from "../templates.js";
11+
12+
export interface CSharpDotConfigs {
13+
".config/csharpier/.csharpierrc.json": string;
14+
".config/cspell/cspell.json": string;
15+
".config/dotnet/.globalconfig": string;
16+
".config/dotnet/Format.targets": string;
17+
".config/dotnet/Packages.props": string;
18+
".config/dotnet/Project.props": string;
19+
".config/dotnet/tools.json": string;
20+
".config/prettier/.prettierrc.json": string;
21+
".config/workspaces/Directory.Build.props": string;
22+
}
23+
24+
export const createCSharpDotConfigs = async (baseConfig: BaseConfigs): Promise<CSharpDotConfigs> => {
25+
const templates = await loadTemplates("csharp", [
26+
".config/csharpier/.csharpierrc.json",
27+
".config/cspell/cspell.json",
28+
".config/dotnet/.globalconfig",
29+
".config/dotnet/Format.targets",
30+
".config/dotnet/Packages.props",
31+
".config/dotnet/Project.props",
32+
".config/dotnet/tools.json",
33+
".config/prettier/.prettierrc.json",
34+
".config/workspaces/Directory.Build.props",
35+
] satisfies (keyof CSharpDotConfigs)[]);
36+
37+
const packageProps = await (async () => {
38+
const options = { preserveOrder: true, ignoreAttributes: false } satisfies X2jOptions;
39+
const content = new XMLParser(options).parse(templates[".config/dotnet/Packages.props"]) as [
40+
{
41+
Project: [object, { ItemGroup: { PackageVersion: []; ":@": { "@_Include": string; "@_Version": string } }[] }];
42+
},
43+
];
44+
const packages = new Map(content[0].Project[1].ItemGroup.map((element) => [element[":@"]["@_Include"], element]));
45+
46+
const cakeFrostingVersion = await getNugetPackageLatestVersion("Cake.Frosting");
47+
const cakeFrostingPackage = packages.get("Cake.Frosting");
48+
if (cakeFrostingPackage === undefined) {
49+
throw new Error("Cake.Frosting package not found in .config/dotnet/Packages.props");
50+
}
51+
cakeFrostingPackage[":@"]["@_Version"] = cakeFrostingVersion;
52+
return new XMLBuilder(options).build(content) as string;
53+
})();
54+
55+
const toolsJson = await (async () => {
56+
const csharpierVersion = await getNugetPackageLatestVersion("csharpier");
57+
const content = JSON.parse(templates[".config/dotnet/tools.json"]) as { tools: { csharpier: { version: string } } };
58+
content.tools.csharpier.version = csharpierVersion;
59+
return stringify(content);
60+
})();
61+
62+
return {
63+
".config/csharpier/.csharpierrc.json": templates[".config/csharpier/.csharpierrc.json"],
64+
".config/cspell/cspell.json": stringify(
65+
mergeArrayComposer(
66+
jsonc.parse(baseConfig[".config/cspell/cspell.json"]) as CSpellUserSettings,
67+
jsonc.parse(templates[".config/cspell/cspell.json"]) as CSpellUserSettings,
68+
),
69+
),
70+
".config/dotnet/.globalconfig": templates[".config/dotnet/.globalconfig"],
71+
".config/dotnet/Format.targets": templates[".config/dotnet/Format.targets"],
72+
".config/dotnet/Packages.props": packageProps,
73+
".config/dotnet/Project.props": templates[".config/dotnet/Project.props"],
74+
".config/dotnet/tools.json": toolsJson,
75+
".config/prettier/.prettierrc.json": stringify(
76+
mergeArrayComposer(
77+
JSON.parse(baseConfig[".config/prettier/.prettierrc.json"]) as object,
78+
JSON.parse(templates[".config/prettier/.prettierrc.json"]) as object,
79+
),
80+
),
81+
".config/workspaces/Directory.Build.props": templates[".config/workspaces/Directory.Build.props"],
82+
};
83+
};

setup/configs/csharp/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { BaseConfigs } from "../base/index.js";
2+
import type { CSharpAutomationConfigs } from "./automation.js";
3+
import { createCSharpAutomationConfigs } from "./automation.js";
4+
import type { CSharpDevcontainerConfigs } from "./devcontainer.js";
5+
import { createCSharpDevcontainerConfigs } from "./devcontainer.js";
6+
import type { CSharpDotConfigs } from "./dotConfig.js";
7+
import { createCSharpDotConfigs } from "./dotConfig.js";
8+
import type { CSharpWorkspaceConfigs } from "./workspace.js";
9+
import { createCSharpWorkspaceConfigs } from "./workspace.js";
10+
11+
export type CSharpConfigs = CSharpDevcontainerConfigs &
12+
CSharpDotConfigs &
13+
CSharpAutomationConfigs &
14+
CSharpWorkspaceConfigs;
15+
16+
export const createCSharpConfigs = async <T extends BaseConfigs>(
17+
baseConfig: T,
18+
remoteUser = "dev",
19+
): Promise<T & CSharpConfigs> => {
20+
const devContainerConfigs = await createCSharpDevcontainerConfigs(baseConfig, remoteUser);
21+
const dotConfigs = await createCSharpDotConfigs(baseConfig);
22+
const automationConfigs = await createCSharpAutomationConfigs();
23+
const workspaceConfigs = await createCSharpWorkspaceConfigs(baseConfig);
24+
return {
25+
...baseConfig,
26+
...devContainerConfigs,
27+
...dotConfigs,
28+
...automationConfigs,
29+
...workspaceConfigs,
30+
};
31+
};

0 commit comments

Comments
 (0)