Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dagger/modules/e2e/discovery.dang
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
6 changes: 3 additions & 3 deletions .dagger/modules/e2e/generate.dang
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.<sdk>.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

Expand Down
9 changes: 1 addition & 8 deletions dagger.json
Original file line number Diff line number Diff line change
@@ -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": "."
}
1 change: 0 additions & 1 deletion dagger.lock
Original file line number Diff line number Diff line change
@@ -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"]
8 changes: 4 additions & 4 deletions mod-config.dang
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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])
}
Expand Down Expand Up @@ -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
}
}
}
9 changes: 3 additions & 6 deletions mod.dang
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}

Expand Down Expand Up @@ -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
}
}
}
90 changes: 20 additions & 70 deletions typescript-sdk.dang
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}

"""
Expand All @@ -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 itthe 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.

Expand All @@ -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 }
Expand Down Expand Up @@ -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.
Expand All @@ -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
}
}

Expand All @@ -281,7 +250,7 @@ type TypescriptSdk {
"""
dev: Boolean! = false,
): Changeset! {
polyfill.workspace(ws).fork.changes
ws.changes
}

"""
Expand Down Expand Up @@ -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,
Expand All @@ -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
}

"""
Expand All @@ -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,
Expand Down