Skip to content

Commit

Permalink
Unfinished changes
Browse files Browse the repository at this point in the history
  • Loading branch information
3timeslazy committed Sep 29, 2024
1 parent a6a6194 commit 7983696
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 28 deletions.
48 changes: 41 additions & 7 deletions src/customizations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function runTests<T, S = keyof Customizations>(key: S, testCases: TestCase<T>[])
} else {
const full: any = {
settings: {},
registries: {},
extensions: [],
};
full[key] = testCase.expect;
Expand All @@ -36,9 +37,14 @@ describe("'customizations' format", () => {
expect: {},
},
{
desc: "invalid jsonc format",
desc: "invalid jsonc format at root",
configFile: "}",
expect: "devcontainer.json must be a valid jsonc file",
expect: "Invalid jsonc at 'root': ValueExpected",
},
{
desc: "invalid jsonc format",
configFile: '{"customizations: {}}',
expect: "Invalid jsonc at 'customizations: {}}': UnexpectedEndOfString",
},
{
desc: "base types are supported",
Expand Down Expand Up @@ -73,11 +79,11 @@ describe("'customizations' format", () => {
desc: `(vscode) ${tc.desc}`,
configFile: tc.configFile.replaceAll("EDITOR", "vscode"),
})));
// runTests(cases.map(tc => ({
// ...tc,
// desc: `(codium) ${tc.desc}`,
// configFile: tc.configFile.replaceAll("EDITOR", "vscodium"),
// })));
runTests("settings", cases.map(tc => ({
...tc,
desc: `(codium) ${tc.desc}`,
configFile: tc.configFile.replaceAll("EDITOR", "vscodium"),
})));
});

describe("vscode.extensions", () => {
Expand All @@ -102,6 +108,21 @@ describe("'customizations' format", () => {
id: "golang.Go"
}]
},
{
desc: "duplicate id",
configFile: `
{
"customizations": {
"vscode": {
"extensions": ["golang.Go", "golang.Go"]
}
}
}
`,
expect: [{
id: "golang.Go"
}]
},
{
desc: "invalid type",
configFile: `
Expand All @@ -114,6 +135,19 @@ describe("'customizations' format", () => {
}
`,
expect: "Invalid configuration at customizations.vscode.extensions: Expected array, received object"
},
{
desc: "extension id is not a string",
configFile: `
{
"customizations": {
"vscode": {
"extensions": ["golang.Go", 1]
}
}
}
`,
expect: "Invalid configuration at customizations.vscode.extensions.1: Expected string, received number"
}
])
})
Expand Down
87 changes: 68 additions & 19 deletions src/customizations.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
import { z } from "zod";
import * as jsonc from "jsonc-parser";
import { readFileSync } from "node:fs";

// Type definitions according to devcontainer specification
// Type definitions according to the devcontainer specification

const SpecSettings = z.object({}).catchall(z.any()).default({});
type SpecSettings = z.infer<typeof SpecSettings>;

const VsCodeExtensions = z.array(z.string()).default([]);
const VsCodeExtensions = z
.array(z.string())
.default([])
.transform(arr => new Set(arr));
type VsCodeExtensions = z.infer<typeof VsCodeExtensions>;

// const CodiumRegistry = z.object({
// url: z.string().url(),
// headers: z.object({}).catchall(z.string()),
// });
// type CodiumRegistry = z.infer<typeof CodiumRegistry>;
const CodiumRegistries = z
.object({})
.catchall(
z.object({
url: z.string().url(),
headers: z.object({}).catchall(z.string()).default({}),
}),
)
.default({});
type CodiumRegistries = z.infer<typeof CodiumRegistries>;

const CodiumExtension = z.object({
registry: z.string().optional(),
version: z.string().optional(),
});
type CodiumExtension = z.infer<typeof CodiumExtension>;

const SpecCustomizations = z.object({
vscode: z.optional(
z.object({
vscode: z
.object({
settings: SpecSettings,
extensions: VsCodeExtensions,
}),
),
})
.optional(),
vscodium: z
.object({
settings: SpecSettings,
registries: CodiumRegistries,
extensions: z.object({}).catchall(CodiumExtension).default({}),
})
.optional(),
});
type SpecCustomizations = z.infer<typeof SpecCustomizations>;

Expand All @@ -40,35 +62,62 @@ export type Extension = {
version?: string;
};

export type Registries = CodiumRegistries;

export type Customizations = {
settings: Settings;
// registries: Record<string, Registry>;
registries: CodiumRegistries;
extensions: Extension[];
};

const emptyCustomuzations = { settings: {}, extensions: [] };
export function parseCustomizationsFile(path: string) {
return parseCustomizations(readFileSync(path, { encoding: "utf-8" }));
}

export function parseCustomizations(configContent: string): Customizations | Error {
let errors: jsonc.ParseError[] = [];
const parsed = jsonc.parse(configContent, errors, { allowTrailingComma: true });
if (errors.length > 0) {
return new Error("devcontainer.json must be a valid jsonc file");
const err = errors[0];
const path = jsonc.getLocation(configContent, err.offset).path;
const msg =
"Invalid jsonc at '" +
(path.length ? path.join(".") : "root") +
"': " +
jsonc.printParseErrorCode(err.error);
return new Error(msg);
}
const validated = DevContainerConfig.safeParse(parsed);
if (validated.error) {
const msg = "Invalid configuration at " +
validated.error.issues[0].path.join(".") + ": " +
validated.error.issues[0].message;
const msg =
"Invalid configuration at " +
validated.error.issues[0].path.join(".") +
": " +
validated.error.issues[0].message;
return new Error(msg);
}

const codium = validated.data.customizations.vscodium;
if (codium) {
for (const [extId, ext] of Object.entries(codium.extensions)) {
if (ext.registry && !codium.registries[ext.registry]) {
return new Error(`Extension "${extId}" refers to unknown "${ext.registry}" registry`);
}
}
return {
settings: codium.settings,
registries: codium.registries,
extensions: Object.entries(codium.extensions).map(([id, ext]) => ({ id, ...ext })),
};
}
const vscode = validated.data.customizations.vscode;
if (vscode) {
return {
settings: vscode.settings,
extensions: vscode.extensions.map(id => ({ id: id })),
registries: {},
extensions: [...vscode.extensions].map(id => ({ id })),
};
}

return emptyCustomuzations;
return { registries: {}, settings: {}, extensions: [] };
}
9 changes: 7 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { installDevpod } from "./devpod";
import { installCodeServer } from "./vscodium/server";
import * as path from "path";
import { DevpodTreeView } from "./treeView";
import { parseCustomizations } from "./spec";
import { parseCustomizationsFile } from "./customizations";
import { downloadExtension, DOWNLOAD_EXTENSIONS_DIR } from "./marketplace";

// TODO: not fail when open vsx in not available
Expand Down Expand Up @@ -89,6 +89,12 @@ async function openContainer(recreate: boolean = false) {
if (!config) {
return;
}

const customizations = parseCustomizationsFile(config.fsPath);
if (customizations instanceof Error) {
vscode.window.showErrorMessage(customizations.message);
return;
}

await upDevpod({
configPath: config.path.replace(workspace.uri.path, ""),
Expand All @@ -105,7 +111,6 @@ async function openContainer(recreate: boolean = false) {
}
const devpodHost = `${devpod.id}.devpod`;

const customizations = parseCustomizations(config.fsPath);
const exts = customizations.extensions;
const installExtArgs = [];
const registryExts = [];
Expand Down

0 comments on commit 7983696

Please sign in to comment.