From ae9c3471d7109126733f999c2a4d48900dbad8d8 Mon Sep 17 00:00:00 2001 From: Guillaume de Rouville Date: Wed, 12 Aug 2026 19:30:21 -0700 Subject: [PATCH 1/2] Remove the polyfill dependency Signed-off-by: Guillaume de Rouville --- .dagger/modules/e2e/discovery.dang | 2 +- .dagger/modules/e2e/generate.dang | 6 +- README.md | 10 ++-- dagger.json | 9 +-- dagger.lock | 1 - mod-config.dang | 8 +-- mod.dang | 9 +-- typescript-sdk.dang | 90 +++++++----------------------- 8 files changed, 36 insertions(+), 99 deletions(-) diff --git a/.dagger/modules/e2e/discovery.dang b/.dagger/modules/e2e/discovery.dang index c1aa098..90d7db3 100644 --- a/.dagger/modules/e2e/discovery.dang +++ b/.dagger/modules/e2e/discovery.dang @@ -1,7 +1,7 @@ """ Checks for `typescriptSdk.modules` — which managed modules are in scope. -Discovery intersects two things: the config directories the polyfill finds from +Discovery intersects two things: the config directories findConfigDirs finds from the client's cwd, and the engine-owned list of modules this SDK manages (`currentModule.asSDK.modules`). So the workspace decides *what* is a module of ours and the cwd decides *how much of it* a command acts on. diff --git a/.dagger/modules/e2e/generate.dang b/.dagger/modules/e2e/generate.dang index ea71c02..735d102 100644 --- a/.dagger/modules/e2e/generate.dang +++ b/.dagger/modules/e2e/generate.dang @@ -25,8 +25,8 @@ type GenerateChecks { emit its typed bindings alongside the module's own — otherwise the module's source cannot import it and the generated tree does not type-check. - Both sides here are dagger-module.toml modules, the CLI 1.0 format: before the - polyfill included dagger-module.toml dir deps in the generated context this + Both sides here are dagger-module.toml modules, the CLI 1.0 format: before + dagger-module.toml dir deps were included in the generated context this failed with "dir module source does not contain a dagger config file". """ generateDependencyCheck(ws: Workspace!): Void @check { @@ -111,7 +111,7 @@ type GenerateChecks { on a re-anchored snapshot), so generating from a subdirectory for real cannot run as a check. Which modules a cwd selects is covered by `discovery:modules-cwd-check`, and re-rooting the result into cwd coordinates - belongs to the polyfill, which checks it in its own generate-from-subdir case. + belongs to the engine, which checks it in its own generate-from-subdir case. """ generateAllScopeCheck(ws: Workspace!): Void @check { let outside = fixtures.discoverySnapshot( diff --git a/README.md b/README.md index 37b40fb..e7bddf2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This module implements the Dagger CLI 1.0 SDK contract: the engine calls its generate`. It also exposes `targetRuntime` (`"typescript"`), so modules it creates run on the built-in TypeScript runtime. -Backed by [`github.com/dagger/polyfill`](https://github.com/dagger/polyfill). +It uses the engine's native `Workspace` and `ModuleSource` APIs directly. ## Install @@ -205,11 +205,9 @@ dagger call dagger-typescript-sdk generate-all-module dagger call dagger-typescript-sdk generate-all-client ``` -`modules` returns the modules this SDK manages — the -`[[modules..as-sdk.modules]]` entries the engine owns — intersected with -the ones visible from your current directory. A module is discovered through the -directory holding its config, so a module whose `source` points elsewhere is -found at its config path, not from inside its source tree. +`modules` returns the registered modules this SDK manages that are in scope from +your current directory: every module at or below it, plus the nearest enclosing +module when the current directory itself is not registered. ## Skipping generation diff --git a/dagger.json b/dagger.json index 28c9169..4b5027b 100644 --- a/dagger.json +++ b/dagger.json @@ -1,15 +1,8 @@ { "name": "typescript-sdk", - "engineVersion": "v1.0.0-0", + "engineVersion": "v1.0.0-beta.10", "sdk": { "source": "dang" }, - "dependencies": [ - { - "name": "polyfill", - "source": "github.com/dagger/polyfill@main", - "pin": "16627066d1852106320bdc0cfa0e5f901efe5970" - } - ], "source": "." } diff --git a/dagger.lock b/dagger.lock index 7e5723d..122494c 100644 --- a/dagger.lock +++ b/dagger.lock @@ -1,3 +1,2 @@ [["version","1"]] ["","git.head",["https://github.com/dagger/sdk-sdk"],"e1747f4b6221fa24da080701e027243e0cc5fa33","float"] -["","git.ref",["https://github.com/dagger/polyfill","main"],"ec3ea84a2351b4beb06ecece951f2e5ef66509ff","float"] \ No newline at end of file diff --git a/mod-config.dang b/mod-config.dang index afc2591..5f295dc 100644 --- a/mod-config.dang +++ b/mod-config.dang @@ -82,7 +82,7 @@ type ModConfig { withPm.withExec(["module-config", "set-base-image", toolPath, baseImage]) } let edited = withImg.file(toolPath).contents - polyfill.workspace(ws).fork.withNewFile(targetFile, edited).changes + ws.withNewFile("/" + targetFile, edited).changes } } @@ -92,7 +92,7 @@ type ModConfig { unsetPackageManager: Changeset! { if (runtime == Runtime.DENO) { # Deno modules carry no package.json, so there is nothing to unset. - polyfill.workspace(ws).fork.changes + ws.changes } else { edit(packageJsonPath, ["module-config", "unset-package-manager", toolPath]) } @@ -176,13 +176,13 @@ type ModConfig { let edit(file: String!, args: [String!]!): Changeset! { if (hasFile(file)) { let edited = tool(file).withExec(args).file(toolPath).contents - polyfill.workspace(ws).fork.withNewFile(file, edited).changes + ws.withNewFile("/" + file, edited).changes } else { # Nothing to unset when the target file is absent. Routing through tool() # would seed the missing file as "{}", making the unset a no-op, and then # write that empty file back as a stray addition (e.g. an empty # package.json in a Deno module). Return an empty Changeset instead. - polyfill.workspace(ws).fork.changes + ws.changes } } } diff --git a/mod.dang b/mod.dang index c4e5d25..ac17f0f 100644 --- a/mod.dang +++ b/mod.dang @@ -51,7 +51,7 @@ type Mod { through the engine rather than assumed equal to rootPath. """ sourcePath: String! { - let subpath = polyfill.workspace(ws).moduleSource("/" + rootPath).core.sourceSubpath + let subpath = ws.moduleSource("/" + rootPath).sourceSubpath if (subpath == "" or subpath == ".") { "." } else { subpath } } @@ -90,12 +90,9 @@ type Mod { """ generate(ws: Workspace!): Changeset! { if (skipGenerate(ws)) { - polyfill.workspace(ws).fork.changes + ws.changes } else { - # Stage the local dependency closure so this module's codegen sees - # up-to-date dependency bindings before generating it. - let stagedWs = ws.withChanges(polyfill.workspace(ws).moduleSource("/" + rootPath).core.generateLocalDependencies(ws)) - polyfill.workspace(stagedWs).moduleSource("/" + rootPath).generate.changes + ws.moduleSource("/" + rootPath).generate(ws).changes } } } diff --git a/typescript-sdk.dang b/typescript-sdk.dang index 69eaec9..417c8d2 100644 --- a/typescript-sdk.dang +++ b/typescript-sdk.dang @@ -41,29 +41,20 @@ type TypescriptSdk { Return every TypeScript SDK module this workspace manages that is visible from the client's current location. - Discovery is anchored at the client's cwd (never the workspace root): the - nearest enclosing module plus every module at or below the cwd, intersected - with the SDK's engine-owned list of managed modules - (currentModule.asSDK.modules). So running from a subdirectory acts on the - project you're in — and the projects beneath it — not the whole workspace. - - Discovery is the polyfill's cwd-aware findConfigDirs (dagger/dagger#13688); - this maps its cwd-relative results to workspace-root-relative paths and keeps - the ones this SDK manages. + The engine selects the modules directly from the workspace's registered SDK + modules: every module at or below the cwd, plus the nearest enclosing module + when the cwd itself is not managed. No filesystem discovery or intersection + is required. A module is found through the directory holding its config, so one whose `source` field points elsewhere is discovered at its config path and not from inside its own source tree. Address those by path (see `mod`). """ modules(ws: Workspace!): [Mod!]! { - let managed = currentModule.asSDK(workspace: ws).modules.{{path}} - let cwd = clientCwd(ws) - polyfill.workspace(ws) - .findConfigDirs(moduleConfigFilenames, exclude: ["**/node_modules/**"]) - .map { dir => moduleRelPath(cwd, dir) } - .uniq - .filter { path => managed.filter { m => m.path == path }.length > 0 } - .map { path => Mod(rootPath: path, ws: ws, skipGenerateFilename: skipGenerateFilename) } + currentModule + .asSDK(workspace: ws) + .modules.{{path}} + .map { module => Mod(rootPath: module.path, ws: ws, skipGenerateFilename: skipGenerateFilename) } } """ @@ -77,32 +68,12 @@ type TypescriptSdk { """ Whether a workspace-root-relative path is in scope from `cwd`: at or below it, - or an ancestor of it — the same cone findConfigDirs walks for modules. + or an ancestor of it. Client entries use the same cwd cone as managed modules. """ let inCwdScope(cwd: String!, path: String!): Boolean! { cwd == "." or path == cwd or path.hasPrefix(cwd + "/") or cwd.hasPrefix(path + "/") } - """ - Resolve a findConfigDirs result — a cwd-relative path, at or below the cwd - ("." , "sub/dir") or a strict ancestor (".." , "../..") — against the cwd into a - workspace-root-relative path, the format both asSDK module paths and - Mod.rootPath use. - """ - let moduleRelPath(cwd: String!, dir: String!): String! { - let base = if (cwd == "" or cwd == ".") { [] } else { cwd.split("/") } - let segs = dir.split("/").reduce(base) { acc, seg => - if (seg == "..") { - acc.dropLast(1) - } else if (seg == "." or seg == "") { - acc - } else { - acc + [seg] - } - } - if (segs.length == 0) { "." } else { segs.join("/") } - } - """ Return the TypeScript SDK module at or above a workspace path. @@ -124,7 +95,7 @@ type TypescriptSdk { let modPath = if (findUp) { # Nearest enclosing module config, regardless of filename order: the deepest # hit wins, so a closer dagger.json is not shadowed by an ancestor - # dagger-module.toml (and vice versa) — matching polyfill findConfigDirs. + # dagger-module.toml (and vice versa) — matching Workspace.findRoots. let foundConfigPath = moduleConfigFilenames.reduce(null) { acc, name => let found = ws.findUp(name, path) if (configHitDepth(found) > configHitDepth(acc)) { found } else { acc } @@ -232,8 +203,6 @@ type TypescriptSdk { rawPath.trimSuffix("/") } - let fork = polyfill.workspace(ws).fork - # An empty name means "the default", not the templates/ directory itself — # which exists, so it would pass the check below and render every starter as # a subdirectory of the new module. @@ -254,7 +223,7 @@ type TypescriptSdk { let renderedSource = renderedTemplate(name, starter, runtime, existing, modPath) let templateSource = configuredTemplate(renderedSource, runtime, packageManager, baseImage) - fork.withDirectory(modPath, templateSource).changes + ws.withNewDirectory("/" + modPath, templateSource).changes } } @@ -281,7 +250,7 @@ type TypescriptSdk { """ dev: Boolean! = false, ): Changeset! { - polyfill.workspace(ws).fork.changes + ws.changes } """ @@ -570,9 +539,8 @@ type TypescriptSdk { """ path: String!, ): Changeset! { - let pws = polyfill.workspace(ws) - let modSrc = pws.moduleSource(module).core - pws.fork.withDirectory(path, clientDirectory( + let modSrc = ws.moduleSource(module) + ws.withNewDirectory("/" + path, clientDirectory( modSrc.clientSchemaIntrospectionJSON.contents, modSrc.moduleOriginalName, modSrc.engineVersion, @@ -594,29 +562,12 @@ type TypescriptSdk { Modules with the generate skip marker are skipped. """ generateAllModule(ws: Workspace!): Changeset! @generate { - let pws = polyfill.workspace(ws) - - let changes = modules(ws) + modules(ws) .filter { mod => mod.skipGenerate(ws) == false } - .map { mod => - # Stage this module's local dependency closure first (leaf-first, possibly - # across SDKs) so its codegen sees up-to-date dependency bindings. The dep - # codegen is ephemeral: taking the changeset against the staged workspace - # cancels it out, leaving only each module's own changes. - let stagedWs = ws.withChanges(pws.moduleSource("/" + mod.rootPath).core.generateLocalDependencies(ws)) - polyfill.workspace(stagedWs).moduleSource("/" + mod.rootPath).generate.changes + .reduce(ws) { stagedWs, mod => + stagedWs.moduleSource("/" + mod.rootPath).generate(stagedWs) } - - # Force the per-module codegen to evaluate concurrently: selecting a field on - # the whole list resolves every element in one pass, where folding them into - # one changeset would walk them one at a time. - changes.{{isEmpty}} - - # Fold onto pws.fork.changes (empty), never ws.changes: under the engine's - # nested ModuleSource.generateLocalDependencies the incoming ws already has a - # dependency closure staged, and re-including it would be re-rooted under the - # dependent and octopus-merged against the same files it just generated. - pws.fork.changes.withChangesets(changes) + .changes } """ @@ -635,15 +586,14 @@ type TypescriptSdk { dependency — skips clients that live elsewhere in the workspace. """ generateAllClient(ws: Workspace!): Changeset! @generate { - let pws = polyfill.workspace(ws) let cwd = clientCwd(ws) currentModule.asSDK(ws).clients .{{path, module, moduleSource.{{ clientSchemaIntrospectionJSON.{{ contents }}, moduleOriginalName, engineVersion, kind, pin, asString }} }} .filter { client => inCwdScope(cwd, client.path) } - .reduce(pws.fork) { fork, client => + .reduce(ws) { stagedWs, client => let m = client.moduleSource - fork.withDirectory(client.path, clientDirectory( + stagedWs.withNewDirectory("/" + client.path, clientDirectory( m.clientSchemaIntrospectionJSON.contents, m.moduleOriginalName, m.engineVersion, From 302708891a25da15669b5a5f0bd1d00a85ade11c Mon Sep 17 00:00:00 2001 From: Guillaume de Rouville Date: Thu, 13 Aug 2026 15:49:49 -0700 Subject: [PATCH 2/2] Use explicit workspace change baselines Signed-off-by: Guillaume de Rouville --- mod-config.dang | 8 ++++---- mod.dang | 4 ++-- typescript-sdk.dang | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mod-config.dang b/mod-config.dang index 5f295dc..415e633 100644 --- a/mod-config.dang +++ b/mod-config.dang @@ -82,7 +82,7 @@ type ModConfig { withPm.withExec(["module-config", "set-base-image", toolPath, baseImage]) } let edited = withImg.file(toolPath).contents - ws.withNewFile("/" + targetFile, edited).changes + ws.withNewFile("/" + targetFile, edited).changes(ws) } } @@ -92,7 +92,7 @@ type ModConfig { unsetPackageManager: Changeset! { if (runtime == Runtime.DENO) { # Deno modules carry no package.json, so there is nothing to unset. - ws.changes + ws.changes(ws) } else { edit(packageJsonPath, ["module-config", "unset-package-manager", toolPath]) } @@ -176,13 +176,13 @@ type ModConfig { let edit(file: String!, args: [String!]!): Changeset! { if (hasFile(file)) { let edited = tool(file).withExec(args).file(toolPath).contents - ws.withNewFile("/" + file, edited).changes + ws.withNewFile("/" + file, edited).changes(ws) } else { # Nothing to unset when the target file is absent. Routing through tool() # would seed the missing file as "{}", making the unset a no-op, and then # write that empty file back as a stray addition (e.g. an empty # package.json in a Deno module). Return an empty Changeset instead. - ws.changes + ws.changes(ws) } } } diff --git a/mod.dang b/mod.dang index ac17f0f..c68e28f 100644 --- a/mod.dang +++ b/mod.dang @@ -90,9 +90,9 @@ type Mod { """ generate(ws: Workspace!): Changeset! { if (skipGenerate(ws)) { - ws.changes + ws.changes(ws) } else { - ws.moduleSource("/" + rootPath).generate(ws).changes + ws.moduleSource("/" + rootPath).generate(ws).changes(ws) } } } diff --git a/typescript-sdk.dang b/typescript-sdk.dang index 417c8d2..6134171 100644 --- a/typescript-sdk.dang +++ b/typescript-sdk.dang @@ -223,7 +223,7 @@ type TypescriptSdk { let renderedSource = renderedTemplate(name, starter, runtime, existing, modPath) let templateSource = configuredTemplate(renderedSource, runtime, packageManager, baseImage) - ws.withNewDirectory("/" + modPath, templateSource).changes + ws.withNewDirectory("/" + modPath, templateSource).changes(ws) } } @@ -250,7 +250,7 @@ type TypescriptSdk { """ dev: Boolean! = false, ): Changeset! { - ws.changes + ws.changes(ws) } """ @@ -551,7 +551,7 @@ type TypescriptSdk { modSrc.asString, modSrc.pin, existingClientConfig(ws, path) - )).changes + )).changes(ws) } """ @@ -567,7 +567,7 @@ type TypescriptSdk { .reduce(ws) { stagedWs, mod => stagedWs.moduleSource("/" + mod.rootPath).generate(stagedWs) } - .changes + .changes(ws) } """ @@ -606,6 +606,6 @@ type TypescriptSdk { existingClientConfig(ws, client.path) )) } - .changes + .changes(ws) } }