Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
959980e
Scribe: Merge Ripley staged refactor plan and update cross-agent history
maorleger May 15, 2026
696359c
feat(typespec-ts): wire client context through new three-layer pipeli…
maorleger May 15, 2026
0ce15dd
test(typespec-ts): add operation adapter unit tests (Stage 2)
maorleger May 15, 2026
0d8d3ba
feat(typespec-ts): expand IR with operation types and adapter functio…
maorleger May 15, 2026
4f8c8cb
test(typespec-ts): add model/enum/union adapter unit tests (Stage 3)
maorleger May 16, 2026
80fbad6
feat(typespec-ts): expand IR with model, enum, and union types (Stage 3)
maorleger May 16, 2026
6170bef
test(typespec-ts): activate model/enum/union adapter tests (Stage 3)
maorleger May 16, 2026
36fa112
Scribe: Stabilize Stage 3 build — fix incomplete adapter implementation
maorleger May 16, 2026
1636722
feat(typespec-ts): wire operations through new three-layer pipeline (…
maorleger May 16, 2026
233bfea
feat(typespec-ts): migrate remaining file categories through new pipe…
maorleger May 16, 2026
62013ec
feat(typespec-ts): wire models and response types through new pipelin…
maorleger May 16, 2026
3a8245f
fix(typespec-ts): fix smoke regressions — LRO spread, duplicate impor…
maorleger May 16, 2026
66c0f95
fix(typespec-ts): fix client-initialization syntax error in azure-mod…
maorleger May 16, 2026
6efd15c
chore: gitignore local squad/team scratch files
maorleger May 18, 2026
b9a628b
docs(typespec-ts): add ARCHITECTURE.md
maorleger May 18, 2026
d4b543a
Surface modular deprecation docs
maorleger May 18, 2026
4382f14
Revert "Surface modular deprecation docs"
maorleger May 19, 2026
d24c617
fix(typespec-ts): include top-level api/** barrels in root index emis…
maorleger May 19, 2026
e35c324
fix(typespec-ts): preserve optional api-version on client context
maorleger May 19, 2026
e8b5a80
fix(typespec-ts): emit models from filtered IR, drop TCGC import from…
maorleger May 19, 2026
56aa9c5
style(typespec-ts): format rewrite regression fixes
maorleger May 19, 2026
9a21858
fix(codegen/models): emit array/dict serializer helpers to resolve B8…
maorleger May 19, 2026
b068e3f
[typespec-ts] migrate model helper types into IR
maorleger May 20, 2026
44dbf42
[typespec-ts] fix extensible enum model emission
maorleger May 20, 2026
30a8e08
chore: untrack stray .squad/ files
maorleger May 28, 2026
6c0f156
feat(typespec-ts-pristine): scaffold north-star emitter package
maorleger May 28, 2026
0d98c08
feat(typespec-ts-pristine): first vertical slice — adapt + renderMode…
maorleger May 28, 2026
9baa130
feat(typespec-ts-pristine): slice 2 — serializer IR + tightened model…
maorleger May 28, 2026
db529fc
feat(typespec-ts-pristine): slice 3 — emitter registration, barrels, …
maorleger May 28, 2026
a334037
feat(typespec-ts-pristine): slices 4+8 — client context + logger
maorleger May 28, 2026
d908a98
feat(typespec-ts-pristine): slice 5 — operations + options
maorleger May 28, 2026
f1606e4
feat(typespec-ts-pristine): slice 6 — classical client class
maorleger May 28, 2026
d7e90a0
feat(typespec-ts-pristine): slice 7 — barrel files, spread structural…
maorleger May 28, 2026
03abed9
feat(typespec-ts-pristine): slice 9 — second fixture validation, over…
maorleger May 28, 2026
e799065
docs: editorial pass
maorleger May 29, 2026
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
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,14 @@ vitest.config.ts.timestamp*
**/test/**/metadata.json
.turbo/
packages/typespec-ts/submodules
.gitmodules
.gitmodules
# Squad: local-only team state (never committed)
.squad/
.squad-workstream
.copilot/
plan.md
.github/agents/squad.agent.md
.github/workflows/squad-heartbeat.yml
.github/workflows/squad-issue-assign.yml
.github/workflows/squad-triage.yml
.github/workflows/sync-squad-labels.yml
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"preinstall": "npx only-allow pnpm",
"purge": "rimraf --glob \"packages/**/node_modules/\"",
"test": "turbo run test",
"update-typespec": "node .scripts/update-typespec-overrides.js"
"update-typespec": "node .scripts/update-typespec-overrides.js",
"compare": "pnpm --dir packages/typespec-ts-pristine compare"
},
"devDependencies": {
"@types/node": "^18.0.0",
Expand Down
45 changes: 45 additions & 0 deletions packages/typespec-ts-pristine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# @azure-tools/typespec-ts-pristine

North-star TypeScript emitter for TypeSpec. Clean-room implementation of a
three-layer pipeline architecture.

## Architecture

```
TCGC SDK Context → Adapter → Code Model (IR) → Renderer → .ts files
```

Three layers, three directories:

| Layer | Directory | Responsibility |
|-------|-----------|----------------|
| Adapter | `src/tcgcadapter/` | Transforms TCGC types into language-specific IR. Only layer that imports TCGC. |
| Code Model | `src/codemodel/` | Pure data types. The contract between adapter and renderer. |
| Renderer | `src/codegen/` | Consumes IR, produces TypeScript source strings. Zero TCGC knowledge. |

## Why does this exist?

The existing `@azure-tools/typespec-ts` emitter grew organically and fuses
adapter and renderer concerns. This package is a greenfield rewrite that
enforces strict layer separation from day one. It targets feature parity with
the existing emitter while being simpler to understand and maintain.

## Comparator

The `compare` script runs both emitters over the same TypeSpec fixture set and
diffs the generated output. It lives in `src/comparator/`.

```bash
# Not yet implemented — interface only
pnpm compare --fixtures ../typespec-test/test/ --baseline ../typespec-ts --candidate .
```

Output: tree diff, per-file unified diff, summary score (% files identical).

## Development

```bash
pnpm install # from repo root
pnpm build # builds this package
pnpm compare # runs comparator (once implemented)
```
198 changes: 198 additions & 0 deletions packages/typespec-ts-pristine/docs/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Design: @azure-tools/typespec-ts-pristine

## What & Why

This package is a clean-room TypeScript emitter for TypeSpec. It follows a
three-layer pipeline architecture:

```
┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ TypeSpec + │ ──▶ │ Adapter │ ──▶ │ Code Model │ ──▶ │ Renderer │ ──▶ .ts files
│ TCGC SDK │ │ (Phase 1) │ │ (IR) │ │ (Phase 3) │
└─────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
```

**Why rewrite?** The existing `@azure-tools/typespec-ts` emitter grew
organically. Adapter and renderer concerns are fused. TCGC types leak into
rendering logic. Symptom-fix dedupe passes accumulate. This package answers:
*"What would we build if we knew all the requirements and started fresh?"*

The answer is boring. Three layers. Each layer has one job. No clever
metaprogramming. The renderer never imports TCGC. The adapter never emits
strings. The code model is the contract.

---

## Surface Area Inventory

The existing emitter produces the following output file categories. Pristine
must cover all of them to achieve parity:

| # | Output Category | Existing Source Location | IR Driver |
|---|----------------|--------------------------|-----------|
| 1 | **Models** (interfaces/types) | `src/modular/emitModels.ts` | `TSModel[]` |
| 2 | **Enums** (type aliases + known values) | `src/modular/emitModels.ts` | `TSEnum[]` |
| 3 | **Unions** (type aliases) | `src/modular/emitModels.ts` | `TSUnion[]` |
| 4 | **Operations** (send/deserialize/public API) | `src/modular/buildOperations.ts`, `src/codegen/operations.ts` | `TSOperation[]` via `TSClient` |
| 5 | **Client context** (factory + interface) | `src/modular/buildClientContext.ts`, `src/codegen/clients.ts` | `TSClient` |
| 6 | **Classical client** (class wrapper) | `src/modular/buildClassicalClient.ts`, `src/codegen/classicalClient.ts` | `TSClient` |
| 7 | **Classical operation groups** | `src/modular/buildClassicalOperationGroups.ts`, `src/codegen/classicalOperations.ts` | `TSOperationGroup[]` |
| 8 | **Options interfaces** | `src/codegen/apiOptions.ts` | `TSOptionsType` per operation |
| 9 | **Serializers** (JSON/XML) | `src/modular/serialization/` | `TSSerializerGroup[]` + `TSModel[]` |
| 10 | **Paging helpers** | `src/modular/static-helpers-metadata.ts` (PagingHelpers) | `TSPagingConfig` |
| 11 | **Polling/LRO helpers** | `src/modular/static-helpers-metadata.ts` (PollingHelpers) | `TSPollingConfig` |
| 12 | **RestorePoller** | `src/modular/buildRestorePoller.ts`, `src/codegen/lroHelpers.ts` | `TSClient.lroConfig` |
| 13 | **Logger** | `src/modular/emitLoggerFile.ts` | `TSGenerationSettings.packageName` |
| 14 | **Index files** (root, subpath, models, api) | `src/modular/buildRootIndex.ts`, `src/modular/buildSubpathIndex.ts`, `src/codegen/indexFiles.ts` | Full `TSCodeModel` |
| 15 | **Package infrastructure** (package.json, tsconfig, etc.) | `src/modular/buildProjectFiles.ts` | `TSGenerationSettings` |
| 16 | **Samples** | `src/modular/emitSamples.ts` | `TSClient` + `TSOperation` |
| 17 | **Tests** | `src/modular/emitTests.ts` | `TSClient` |
| 18 | **Response type aliases** | `src/codegen/responseTypes.ts` | `TSOperation.returnType` |
| 19 | **Static helpers** (URL template, multipart, platform types) | `src/modular/static-helpers-metadata.ts` | `TSHelperFile[]` |

---

## IR Shapes (The Contract)

Each surface is driven by specific IR types. These are defined in
`src/codemodel/index.ts`. Key types:

| IR Type | Drives | Key Fields |
|---------|--------|------------|
| `TSCodeModel` | Root — everything | clients, models, enums, unions, serializers, helpers, settings |
| `TSGenerationSettings` | Package config, infra files | packageName, flavor, isArm, outputDir |
| `TSClient` | Client context + classical class | name, parameters, endpoint, methods, operationGroups, children |
| `TSOperation` | Operation files + options | name, kind, httpMethod, path, parameters, returnType, optionsType |
| `TSOperationGroup` | Grouped operation files | name, operations |
| `TSModel` | Model interfaces + serializers | name, properties, baseModel, discriminator, needsSerializer |
| `TSEnum` | Enum type aliases | name, members, isExtensible, valueType |
| `TSUnion` | Union type aliases | name, variants, discriminator |
| `TSSerializerGroup` | Serializer files | contentType, models |
| `TSHelperFile` | Static helper copies | outputPath, category |
| `TSPagingConfig` | Paging helper inclusion | hasPaging, itemPropertyPath |
| `TSPollingConfig` | LRO helper inclusion | hasLro, emitRestorePoller |
| `TSParameter` | Shared parameter shape | name, type, required, defaultValue |
| `TSProperty` | Model/options properties | name, type, optional, readonly, serializedName |
| `TSDiscriminator` | Polymorphic hierarchies | propertyName, value, variants |
| `TSOptionsType` | Per-operation options bag | name, properties |
| `TSEndpoint` | Client endpoint config | urlTemplate, isParameterized, templateParams |
| `TSApiVersion` | API versioning | paramName, defaultValue, isInEndpoint |

---

## Non-Negotiable Invariants

1. **Renderer does not import TCGC.** Not transitively, not via re-export, not
via a "utils" file that sneaks it in. If the renderer needs data, it goes
in the code model.

2. **Code model is the contract.** The adapter's output type is `TSCodeModel`.
The renderer's input type is `TSCodeModel`. That's the only coupling.

3. **No symptom-fix dedupe passes.** If duplicate imports appear, the adapter
is producing bad data. Fix the adapter. Don't add a post-processing strip
pass.

4. **No clever metaprogramming.** No code that generates code that generates
code. Stick to string builders or template literals: boring and predictable.

5. **File-per-concern.** Each renderer function produces one logical file kind.
No 500-line functions that emit three different file types.

6. **Pure data code model.** No methods on IR types. No side effects. No
closures. Serializable to JSON.

7. **Self-contained. No internal workspace dependencies. Always extractable.**
This package has ZERO dependencies on other packages in this monorepo — not
`@azure-tools/rlc-common`, not `@azure-tools/typespec-ts`, nothing. The
only allowed dependencies are external npm packages (`@typespec/*`,
`@azure-tools/typespec-client-generator-core`, `ts-morph`, `tslib`, etc.).
If a utility exists in a sibling package and we need it, we copy it in
(with attribution). The package must be liftable to its own repo at any
time: `cp -r packages/typespec-ts-pristine ../new-repo/ && npm install`
must work.

---

## Explicit Non-Goals

- **AutoRest parity.** The `autorest.typescript` package is in maintenance
mode. Pristine targets TypeSpec-only generation.

- **Experimental flags.** No `enableExperimentalFeature` toggles. Features are
either implemented or they aren't.

- **Legacy customer overlays.** No hooks for customers to patch generated code
inside the emitter. That's a migration concern, not a design concern.

- **RLC generation.** Pristine generates modular SDK only. RLC is handled by
the existing emitter in maintenance mode, or by a separate focused package.

---

## Comparator Approach

A `compare` script validates pristine output against the existing emitter.

### Interface

```
compare(fixturesDir, baselineOutput, candidateOutput) → CompareResult
```

### Location

`src/comparator/index.ts` — types and orchestration logic.
`pnpm compare` — CLI entry (package.json script).

### What it does

1. Enumerates fixture directories under `packages/typespec-test/test/`
2. For each fixture, globs all `.ts` files in both output trees
3. Computes: files only in baseline, files only in candidate, files with diffs
4. Produces per-file unified diffs
5. Calculates a score: `(identical files / total files) × 100`

### Output format

```
Fixture: azure/storage-blob
Score: 94.2% (47/50 files identical)
Missing in candidate: src/models/legacy.ts
Extra in candidate: (none)
Diffs:
src/api/containers/operations.ts (12 lines changed)
src/index.ts (3 lines changed)
```

### What it does NOT do

- Does not compile the output (that's the smoke test's job)
- Does not run integration tests (that's the integration suite's job)
- Does not evaluate which output is "better" — only equivalence
- Does not import the baseline emitter as a dependency — it either invokes it
as a subprocess (`npx @azure-tools/typespec-ts`) or diffs pre-generated
output that already exists in `packages/typespec-test/test/*/generated/`

---

## Directory Structure

```
packages/typespec-ts-pristine/
├── package.json
├── tsconfig.json
├── README.md
├── docs/
│ └── DESIGN.md ← this file
└── src/
├── index.ts ← emitter entry point (3-phase orchestrator)
├── tcgcadapter/
│ └── index.ts ← TCGC → TSCodeModel transformation
├── codemodel/
│ └── index.ts ← IR type definitions (pure data)
├── codegen/
│ └── index.ts ← TSCodeModel → .ts file strings
└── comparator/
└── index.ts ← diff tool for validating output equivalence
```
41 changes: 41 additions & 0 deletions packages/typespec-ts-pristine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@azure-tools/typespec-ts-pristine",
"version": "0.1.0",
"description": "North-star TypeScript emitter for TypeSpec — clean three-layer pipeline architecture",
"main": "dist/src/index.js",
"typespec": {
"emitter": {
"main": "./dist/src/index.js"
}
},
"type": "module",
"exports": {
".": {
"default": "./dist/src/index.js",
"types": "./dist/src/index.d.ts"
}
},
"scripts": {
"build": "tsc -p .",
"clean": "rimraf ./dist",
"compare": "node dist/src/comparator/index.js"
},
"author": "Microsoft Corporation",
"license": "MIT",
"devDependencies": {
"@azure-tools/typespec-client-generator-core": "^0.68.0",
"@typespec/compiler": "^1.12.0",
"@typespec/http": "^1.12.0",
"@typespec/rest": "^0.82.0",
"@typespec/versioning": "^0.82.0",
"typescript": "~5.6.3"
},
"peerDependencies": {
"@azure-tools/typespec-client-generator-core": "^0.68.0",
"@typespec/compiler": "^1.12.0"
},
"dependencies": {
"ts-morph": "^23.0.0",
"tslib": "^2.3.1"
}
}
Loading