Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Use go:embed for generator definitions and add index.d.ts from vscode…
… repo
  • Loading branch information
JeffreyCA committed Oct 26, 2025
commit a8cc149dcc8a0e9a324cd4028902301b7e58d5e1
18 changes: 14 additions & 4 deletions cli/azd/docs/fig-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ The Fig spec generation is implemented in `cli/azd/internal/figspec/`:
figspec/
├── types.go # Core data structures and interfaces
├── spec_builder.go # Main spec generation logic
├── fig_generators.go # Dynamic generator definitions
├── fig_generators.go # Generator constants and embeddings
├── typescript_renderer.go # TypeScript code generation
└── customizations.go # azd-specific customizations
├── customizations.go # azd-specific customizations
└── resources/
├── generators.ts # TypeScript generator implementations (embedded)
└── index.d.ts # Type definitions
```

### Generation flow
Expand Down Expand Up @@ -120,9 +123,16 @@ Implements customization interfaces to add azd-specific intelligence:
- **`CustomArgsProvider`**: Custom argument patterns (e.g. `azd env set [key] [value]`)
- **`CustomFlagArgsProvider`**: Custom flag argument names (e.g. `from-package` → `file-path|image-tag`)

#### 3. Generators (`fig_generators.go`)
#### 3. Generators (`fig_generators.go` + `resources/generators.ts`)

Defines [**generators**](https://fig.io/docs/reference/generator/basic) that execute azd commands to dynamically provide context-aware suggestions. The generators are implemented as TypeScript code embedded inline within `fig_generators.go`:
Defines [**generators**](https://fig.io/docs/reference/generator/basic) that execute azd commands to dynamically provide context-aware suggestions.

The generator implementations are written in TypeScript and stored in `resources/generators.ts`, which is embedded into the Go binary using Go's `embed` directive. The `fig_generators.go` file contains:
- Constants that reference each generator (e.g., `FigGenListEnvironments`)
- The embed directive to include `generators.ts`
- Mapping between Go constants and TypeScript generator keys

The `resources/index.d.ts` file contains a copy of [VS Code's Fig type definitions](https://github.com/microsoft/vscode/blob/main/extensions/terminal-suggest/src/completions/index.d.ts), enabling IntelliSense and type checking when editing `generators.ts` in an IDE.

| Generator | Command | Purpose |
|-----------|---------|---------|
Expand Down
190 changes: 6 additions & 184 deletions cli/azd/internal/figspec/fig_generators.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

package figspec

import _ "embed"

// Fig generator names used for dynamic autocomplete suggestions.
// These constants map to the TypeScript generator implementations in the rendered Fig spec.
// These constants map to the TypeScript generator implementations defined in resources/figspec-generators.ts.
// The constant names should match the keys in the azdGenerators object (e.g., FigGenListEnvironments -> listEnvironments).
const (
// FigGenListEnvironments generates suggestions from available azd environments
FigGenListEnvironments = "azdGenerators.listEnvironments"
Expand All @@ -28,186 +31,5 @@ const (
FigGenListInstalledExtensions = "azdGenerators.listInstalledExtensions"
)

// figGeneratorDefinitions returns the TypeScript code defining all Fig generators and interfaces used by them.
// The generator names in this code must match the constants defined above.
func figGeneratorDefinitions() string {
return `interface AzdEnvListItem {
Name: string;
DotEnvPath: string;
HasLocal: boolean;
HasRemote: boolean;
IsDefault: boolean;
}

interface AzdTemplateListItem {
name: string;
description: string;
repositoryPath: string;
tags: string[];
}

interface AzdExtensionListItem {
id: string;
name: string;
namespace: string;
version: string;
installedVersion: string;
source: string;
}

const azdGenerators: Record<string, Fig.Generator> = {
listEnvironments: {
script: ['azd', 'env', 'list', '--output', 'json'],
postProcess: (out) => {
try {
const envs: AzdEnvListItem[] = JSON.parse(out);
return envs.map((env) => ({
name: env.Name,
displayName: env.IsDefault ? 'Default' : undefined,
}));
} catch {
return [];
}
},
},
listEnvironmentVariables: {
script: ['azd', 'env', 'get-values', '--output', 'json'],
postProcess: (out) => {
try {
const envVars: Record<string, string> = JSON.parse(out);
return Object.keys(envVars).map((key) => ({
name: key,
}));
} catch {
return [];
}
},
},
listTemplates: {
script: ['azd', 'template', 'list', '--output', 'json'],
postProcess: (out) => {
try {
const templates: AzdTemplateListItem[] = JSON.parse(out);
return templates.map((template) => ({
name: template.repositoryPath,
description: template.name,
}));
} catch {
return [];
}
},
cache: {
strategy: 'stale-while-revalidate',
}
},
listTemplateTags: {
script: ['azd', 'template', 'list', '--output', 'json'],
postProcess: (out) => {
try {
const templates: AzdTemplateListItem[] = JSON.parse(out);
const tagsSet = new Set<string>();

// Collect all unique tags from all templates
templates.forEach((template) => {
if (template.tags && Array.isArray(template.tags)) {
template.tags.forEach((tag) => tagsSet.add(tag));
}
});

// Convert set to array and return as suggestions
return Array.from(tagsSet).sort().map((tag) => ({
name: tag,
}));
} catch {
return [];
}
},
cache: {
strategy: 'stale-while-revalidate',
}
},
listTemplatesFiltered: {
custom: async (tokens, executeCommand, generatorContext) => {
// Find if there's a -f or --filter flag in the tokens
let filterValue: string | undefined;
for (let i = 0; i < tokens.length; i++) {
if ((tokens[i] === '-f' || tokens[i] === '--filter') && i + 1 < tokens.length) {
filterValue = tokens[i + 1];
break;
}
}

// Build the azd command with filter if present
const args = ['template', 'list', '--output', 'json'];
if (filterValue) {
args.push('--filter', filterValue);
}

try {
const { stdout } = await executeCommand({
command: 'azd',
args: args,
});

const templates: AzdTemplateListItem[] = JSON.parse(stdout);
return templates.map((template) => ({
name: template.repositoryPath,
description: template.name,
}));
} catch {
return [];
}
},
cache: {
strategy: 'stale-while-revalidate',
}
},
listExtensions: {
script: ['azd', 'ext', 'list', '--output', 'json'],
postProcess: (out) => {
try {
const extensions: AzdExtensionListItem[] = JSON.parse(out);
const uniqueExtensions = new Map<string, AzdExtensionListItem>();

extensions.forEach((ext) => {
if (!uniqueExtensions.has(ext.id)) {
uniqueExtensions.set(ext.id, ext);
}
});

return Array.from(uniqueExtensions.values()).map((ext) => ({
name: ext.id,
description: ext.name,
}));
} catch {
return [];
}
},
cache: {
strategy: 'stale-while-revalidate',
}
},
listInstalledExtensions: {
script: ['azd', 'ext', 'list', '--installed', '--output', 'json'],
postProcess: (out) => {
try {
const extensions: AzdExtensionListItem[] = JSON.parse(out);
const uniqueExtensions = new Map<string, AzdExtensionListItem>();

extensions.forEach((ext) => {
if (!uniqueExtensions.has(ext.id)) {
uniqueExtensions.set(ext.id, ext);
}
});

return Array.from(uniqueExtensions.values()).map((ext) => ({
name: ext.id,
description: ext.name,
}));
} catch {
return [];
}
},
},
};`
}
//go:embed resources/generators.ts
var figGeneratorDefinitionsTS string
Loading
Loading