diff --git a/helpers/config-updator/config-updator b/helpers/config-updator/config-updator new file mode 100755 index 0000000..41eacd9 Binary files /dev/null and b/helpers/config-updator/config-updator differ diff --git a/helpers/config-updator/main.go b/helpers/config-updator/main.go index 03c67b2..bd62318 100644 --- a/helpers/config-updator/main.go +++ b/helpers/config-updator/main.go @@ -114,6 +114,15 @@ func updatePackageJSON(packageJSON string) (string, error) { return "", fmt.Errorf("set type=module: %w", err) } + // Pin typescript unless the module already chose a version. The runtime + // mounts its own prebuilt copy — and skips dependency installation for an + // otherwise dependency-free module — only when the pin matches the engine's + // default, so drifting from it silently turns every call into an install. + packageJSON, err = setIfNotExists(packageJSON, "dependencies.typescript", defaultTypeScriptVersion) + if err != nil { + return "", fmt.Errorf("set typescript dependency: %w", err) + } + // Remove legacy in-tree @dagger.io/dagger deps so we transition cleanly to // the engine-managed bundle. Matches dagger/dagger UpdatePackageJSONForModule. for _, key := range []string{ @@ -307,7 +316,14 @@ func updateTSConfig(tsConfig string) (string, error) { } func updateDenoConfig(denoConfig string) (string, error) { - denoConfig, err := sjson.Set(denoConfig, "nodeModulesDir", "auto") + // Deno resolves dependencies through this map rather than node_modules, so + // the compiler the module's own code needs has to be declared here. + denoConfig, err := setIfNotExists(denoConfig, "imports.typescript", "npm:typescript@"+defaultTypeScriptVersion) + if err != nil { + return "", fmt.Errorf("set typescript import: %w", err) + } + + denoConfig, err = sjson.Set(denoConfig, "nodeModulesDir", "auto") if err != nil { return "", fmt.Errorf("set nodeModulesDir: %w", err) } diff --git a/helpers/config-updator/main_test.go b/helpers/config-updator/main_test.go index 85c9c9e..23335fc 100644 --- a/helpers/config-updator/main_test.go +++ b/helpers/config-updator/main_test.go @@ -18,7 +18,25 @@ func TestUpdatePackageJSON(t *testing.T) { { name: "empty package.json", packageJSON: `{}`, - expected: `{"type": "module"}`, + expected: `{"type": "module", "dependencies": {"typescript": "5.9.3"}}`, + }, + { + // The runtime mounts its prebuilt compiler only when the pin matches + // its default, so a module that chose its own version keeps it and + // accepts the install rather than being silently retargeted. + name: "a user's own typescript pin is preserved", + packageJSON: `{ + "type": "module", + "dependencies": { + "typescript": "5.4.0" + } +}`, + expected: `{ + "type": "module", + "dependencies": { + "typescript": "5.4.0" + } +}`, }, { name: "package.json with local dagger dependency is stripped", @@ -87,13 +105,16 @@ func TestUpdatePackageJSON(t *testing.T) { "scripts": { "build": "tsc" }, - "type": "module" + "type": "module", + "dependencies": { + "typescript": "5.9.3" + } }`, }, { - name: "type=module already set is a no-op", + name: "type=module already set still gains the typescript pin", packageJSON: `{"type": "module"}`, - expected: `{"type": "module"}`, + expected: `{"type": "module", "dependencies": {"typescript": "5.9.3"}}`, }, } { t.Run(tc.name, func(t *testing.T) { @@ -216,6 +237,7 @@ func TestUpdateDenoConfig(t *testing.T) { denoConfig: `{}`, expected: `{ "imports": { + "typescript": "npm:typescript@5.9.3", "@dagger.io/dagger": "./sdk/index.ts", "@dagger.io/dagger/telemetry": "./sdk/telemetry.ts" }, @@ -235,6 +257,7 @@ func TestUpdateDenoConfig(t *testing.T) { name: "deno.json with dagger imports already set is idempotent", denoConfig: `{ "imports": { + "typescript": "npm:typescript@5.9.3", "@dagger.io/dagger": "./sdk/index.ts", "@dagger.io/dagger/telemetry": "./sdk/telemetry.ts" }, @@ -251,6 +274,7 @@ func TestUpdateDenoConfig(t *testing.T) { }`, expected: `{ "imports": { + "typescript": "npm:typescript@5.9.3", "@dagger.io/dagger": "./sdk/index.ts", "@dagger.io/dagger/telemetry": "./sdk/telemetry.ts" }, @@ -273,6 +297,7 @@ func TestUpdateDenoConfig(t *testing.T) { }`, expected: `{ "imports": { + "typescript": "npm:typescript@5.9.3", "@dagger.io/dagger": "./sdk/index.ts", "@dagger.io/dagger/telemetry": "./sdk/telemetry.ts" }, @@ -287,6 +312,33 @@ func TestUpdateDenoConfig(t *testing.T) { "node-globals", "byonm" ] +}`, + }, + { + // Deno has no node_modules to fall back on, so the compiler has to be + // declared here — but a user who picked a version keeps it. + name: "a user's own typescript import is preserved", + denoConfig: `{ + "imports": { + "typescript": "npm:typescript@5.4.0" + } +}`, + expected: `{ + "imports": { + "typescript": "npm:typescript@5.4.0", + "@dagger.io/dagger": "./sdk/index.ts", + "@dagger.io/dagger/telemetry": "./sdk/telemetry.ts" + }, + "nodeModulesDir": "auto", + "compilerOptions": { + "experimentalDecorators": true + }, + "unstable": [ + "bare-node-builtins", + "sloppy-imports", + "node-globals", + "byonm" + ] }`, }, { @@ -304,6 +356,7 @@ func TestUpdateDenoConfig(t *testing.T) { "dev": "deno run main.ts" }, "imports": { + "typescript": "npm:typescript@5.9.3", "@user/lib": "./src/lib.ts", "@dagger.io/dagger": "./sdk/index.ts", "@dagger.io/dagger/telemetry": "./sdk/telemetry.ts" @@ -329,6 +382,7 @@ func TestUpdateDenoConfig(t *testing.T) { expected: `{ "url": "https://foo/bar/baz.html", "imports": { + "typescript": "npm:typescript@5.9.3", "@dagger.io/dagger": "./sdk/index.ts", "@dagger.io/dagger/telemetry": "./sdk/telemetry.ts" },