Skip to content
Merged
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
31 changes: 25 additions & 6 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -3434,6 +3434,24 @@ limitation in the facade plugin currently means `--services` filtering does not
work over a multi-file descriptor set from these two sources; use the local
source, or omit `--services` to generate the full contract's facade.

TypeScript's generated entry point (`src/index.ts`) has three parts. Each facade
is re-exported flat under the name its plugin gives it (`accounts`,
`AccountsClient`). Each proto package is one namespace named from the package —
its components joined by `_`, an underscore inside a component doubled, and
`_pb` appended — so `saas.accounts.v1` is `saas_accounts_v1_pb` and `a_b.v1`
(`a__b_v1_pb`) stays distinct from `a.b_v1` (`a_b__v1_pb`); moving a message
between files of one package renames nothing. Every name only one package
declares is also re-exported flat, so `import { Installation, AuditService }`
keeps working. A name two packages declare (protobuf keeps names unique within a
package, not across the dependency graph) is not exported flat: a comment at the
top of the entry point lists it with each namespace that reaches it, for example
`codefly_base_v0_pb.WorkContextV1` and `host_authority_v1_pb.WorkContextV1`.
Generation refuses, naming both, when two facades or package-less files would
claim one name, since nothing else would reach either. The package barrels live
in `src/packages/<package>.ts`. The scaffolded `package.json` exports only `.`,
so the entry point is a package consumer's only way in; paths under `src/gen/`
are reachable only from inside the library.

##### Library layout

```
Expand All @@ -3443,17 +3461,18 @@ libraries/saas-starter-accounts-client/
catalog.codefly.json # the endpoint's catalog entry (subset if --services)
contract.binpb | openapi.json
go/ go.mod, gen/…, accounts_facade.pb.go
typescript/ package.json, tsconfig.json, src/index.ts, src/gen/…, src/accounts_facade.ts
typescript/ package.json, tsconfig.json, src/index.ts, src/packages/…, src/gen/…, src/accounts_facade.ts
python/ pyproject.toml, <pkg>/_gen/…, <pkg>/accounts.py
```

The TypeScript library is an ESM package whose root resolves to `dist/index.js`
(types `dist/index.d.ts`). `src/index.ts` is generated output, rewritten on
every run: a barrel re-exporting every facade and every generated binding, so
the package name alone reaches every client and message schema. The scaffold
(`package.json`, `tsconfig.json`) is written once and declares a `typescript`
dev dependency and a `build` script (`tsc -p tsconfig.json`) that produces
`dist/` — the only directory, with the README, that the package ships.
every run: a barrel re-exporting every facade and every generated binding
(shaped as above), so the package name alone reaches every client and message
schema. The scaffold (`package.json`, `tsconfig.json`) is written once and
declares a `typescript` dev dependency and a `build` script (`tsc -p
tsconfig.json`) that produces `dist/` — the only directory, with the README,
that the package ships.

The Python package root is not a purely generated directory either. A run
replaces `<pkg>/_gen/` whole and, at the root, only the files a protoc plugin's
Expand Down
42 changes: 0 additions & 42 deletions pkg/generators/client_library.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"context"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -126,47 +125,6 @@ func finalizeTypeScriptLibrary(langDir, npmPackage string) (generatedLanguageExp
return generatedLanguageExport{Name: "typescript", Path: "typescript/", Exports: []string{npmPackage}}, nil
}

// writeTypeScriptIndex writes src/index.ts: the barrel re-exporting every
// hoisted facade and every generated binding, and so the one file
// package.json's "exports" names. It is generated output, not a scaffold —
// src/ is wholly owned by this command (wipeTypeScriptLibrary removes it) and
// which files exist depends on the contracts generated — so it is rewritten on
// every run instead of written once. Without it the library declares an entry
// point nothing produced and cannot be resolved at all.
func writeTypeScriptIndex(langDir string) error {
srcDir := filepath.Join(langDir, "src")
if err := os.MkdirAll(srcDir, 0o755); err != nil {
return err
}
index := filepath.Join(srcDir, "index.ts")
var modules []string
if err := filepath.WalkDir(srcDir, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() || path == index || !strings.HasSuffix(entry.Name(), ".ts") {
return nil
}
relative, relErr := filepath.Rel(srcDir, path)
if relErr != nil {
return relErr
}
// The ".js" specifier the generated bindings and facades already use:
// the sources are ESM and resolve by emitted path, not by source name.
modules = append(modules, "./"+strings.TrimSuffix(filepath.ToSlash(relative), ".ts")+".js")
return nil
}); err != nil {
return err
}
sort.Strings(modules)
var content strings.Builder
content.WriteString("// generated by codefly generate client; do not edit\n")
for _, module := range modules {
fmt.Fprintf(&content, "export * from %q;\n", module)
}
return os.WriteFile(index, []byte(content.String()), 0o644) //nolint:gosec
}

// wipePythonLibrary clears pkgDir's generated content once, before any entry
// is merged in: _gen/, which is entirely its own, and each facade at the
// package root — a file whose generated-code notice names a protoc plugin,
Expand Down
Loading
Loading