Skip to content

Commit 55acfe3

Browse files
authored
Support scaffolding for JavaScript based extensions. (#5121)
Adds javascript support for azd developer extension.
1 parent 8859586 commit 55acfe3

22 files changed

+885
-0
lines changed

cli/azd/extensions/microsoft.azd.extensions/build.ps1

+18
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ foreach ($PLATFORM in $PLATFORMS) {
8282
}
8383

8484
Rename-Item -Path "$OUTPUT_DIR/$EXPECTED_OUTPUT_NAME" -NewName $OUTPUT_NAME
85+
} elseif ($env:EXTENSION_LANGUAGE -eq "javascript") {
86+
$ENTRY_FILE = "pkg-entry.js"
87+
$TARGET = "node16-$OS-x64"
88+
$EXPECTED_OUTPUT_NAME = "$EXTENSION_ID_SAFE-$OS-$ARCH"
89+
if ($OS -eq "windows") {
90+
$EXPECTED_OUTPUT_NAME += ".exe"
91+
}
92+
93+
Write-Host "Installing dependencies..."
94+
npm install
95+
96+
Write-Host "Building JavaScript extension for $OS/$ARCH..."
97+
pkg $ENTRY_FILE -o $OUTPUT_DIR/$EXPECTED_OUTPUT_NAME --targets $TARGET --config package.json
98+
99+
if ($LASTEXITCODE -ne 0) {
100+
Write-Host "An error occurred while building for $OS/$ARCH"
101+
exit 1
102+
}
85103
} elseif ($env:EXTENSION_LANGUAGE -eq "go") {
86104
# Set environment variables for Go build
87105
$env:GOOS = $OS

cli/azd/extensions/microsoft.azd.extensions/internal/cmd/init.go

+14
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ func collectExtensionMetadata(ctx context.Context, azdClient *azdext.AzdClient)
367367
Label: "C#",
368368
Value: "dotnet",
369369
},
370+
{
371+
Label: "JavaScript",
372+
Value: "javascript",
373+
},
370374
{
371375
Label: "Python",
372376
Value: "python",
@@ -467,6 +471,16 @@ func createExtensionDirectory(
467471
return fmt.Errorf("failed to copy and process templates: %w", err)
468472
}
469473

474+
if extensionMetadata.Language == "dotnet" || extensionMetadata.Language == "javascript" {
475+
protoSrcPath := path.Join("languages", "proto")
476+
protoDstPath := filepath.Join(extensionPath, "proto")
477+
478+
err = copyAndProcessTemplates(resources.Languages, protoSrcPath, protoDstPath, templateMetadata)
479+
if err != nil {
480+
return fmt.Errorf("failed to copy and process proto templates: %w", err)
481+
}
482+
}
483+
470484
// Create the extension.yaml file
471485
yamlBytes, err := yaml.Marshal(extensionMetadata)
472486
if err != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const grpc = require('@grpc/grpc-js');
2+
const protoLoader = require('@grpc/proto-loader');
3+
const path = require('path');
4+
const fs = require('fs');
5+
6+
class AzdClient {
7+
constructor() {
8+
const server = process.env.AZD_SERVER;
9+
const token = process.env.AZD_ACCESS_TOKEN;
10+
11+
if (!server || !token) {
12+
throw new Error('AZD_SERVER and AZD_ACCESS_TOKEN must be set.');
13+
}
14+
15+
this._metadata = new grpc.Metadata();
16+
this._metadata.add('authorization', token);
17+
18+
const options = {
19+
keepCase: true,
20+
longs: String,
21+
enums: String,
22+
defaults: true,
23+
oneofs: true,
24+
};
25+
26+
// Loads a .proto file and returns the generated gRPC package
27+
const loadService = (name) => {
28+
let protoPath;
29+
if (process.pkg) {
30+
protoPath = path.join(path.dirname(process.execPath), 'proto', `${name}.proto`);
31+
if (!fs.existsSync(protoPath)) {
32+
protoPath = path.join(__dirname, 'proto', `${name}.proto`);
33+
}
34+
} else {
35+
protoPath = path.join(process.cwd(), 'proto', `${name}.proto`);
36+
}
37+
38+
const packageDefinition = protoLoader.loadSync(protoPath, options);
39+
const proto = grpc.loadPackageDefinition(packageDefinition);
40+
41+
return proto.azdext;
42+
};
43+
44+
const address = server.replace(/^https?:\/\//, '');
45+
46+
// Load each proto service
47+
const composeProto = loadService('compose');
48+
const deploymentProto = loadService('deployment');
49+
const environmentProto = loadService('environment');
50+
const eventProto = loadService('event');
51+
const projectProto = loadService('project');
52+
const promptProto = loadService('prompt');
53+
const userConfigProto = loadService('user_config');
54+
const workflowProto = loadService('workflow');
55+
56+
this.Compose = new composeProto.ComposeService(address, grpc.credentials.createInsecure());
57+
this.Deployment = new deploymentProto.DeploymentService(address, grpc.credentials.createInsecure());
58+
this.Environment = new environmentProto.EnvironmentService(address, grpc.credentials.createInsecure());
59+
this.Events = new eventProto.EventService(address, grpc.credentials.createInsecure());
60+
this.Project = new projectProto.ProjectService(address, grpc.credentials.createInsecure());
61+
this.Prompt = new promptProto.PromptService(address, grpc.credentials.createInsecure());
62+
this.UserConfig = new userConfigProto.UserConfigService(address, grpc.credentials.createInsecure());
63+
this.Workflow = new workflowProto.WorkflowService(address, grpc.credentials.createInsecure());
64+
}
65+
}
66+
67+
module.exports = AzdClient;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Get the directory of the script
2+
$EXTENSION_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
3+
4+
# Change to the script directory
5+
Set-Location -Path $EXTENSION_DIR
6+
7+
# Create a safe version of EXTENSION_ID replacing dots with dashes
8+
$EXTENSION_ID_SAFE = $env:EXTENSION_ID -replace '\.', '-'
9+
10+
# Define output directory
11+
$OUTPUT_DIR = if ($env:OUTPUT_DIR) { $env:OUTPUT_DIR } else { Join-Path $EXTENSION_DIR "bin" }
12+
13+
# Create output directory if it doesn't exist
14+
if (-not (Test-Path -Path $OUTPUT_DIR)) {
15+
New-Item -ItemType Directory -Path $OUTPUT_DIR | Out-Null
16+
}
17+
18+
# Get Git commit hash and build date
19+
$COMMIT = git rev-parse HEAD
20+
$BUILD_DATE = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
21+
22+
# List of OS and architecture combinations
23+
if ($env:EXTENSION_PLATFORM) {
24+
$PLATFORMS = @($env:EXTENSION_PLATFORM)
25+
} else {
26+
$PLATFORMS = @(
27+
"windows/amd64",
28+
"windows/arm64",
29+
"darwin/amd64",
30+
"darwin/arm64",
31+
"linux/amd64",
32+
"linux/arm64"
33+
)
34+
}
35+
36+
$APP_PATH = "github.com/azure/azure-dev/cli/azd/extensions/$env:EXTENSION_ID/internal/cmd"
37+
38+
# Check if the build type is specified
39+
if (-not $env:EXTENSION_LANGUAGE) {
40+
Write-Host "Error: BUILD_TYPE environment variable is required (go or dotnet)"
41+
exit 1
42+
}
43+
44+
# Loop through platforms and build
45+
foreach ($PLATFORM in $PLATFORMS) {
46+
$OS, $ARCH = $PLATFORM -split '/'
47+
48+
$OUTPUT_NAME = Join-Path $OUTPUT_DIR "$EXTENSION_ID_SAFE-$OS-$ARCH"
49+
50+
if ($OS -eq "windows") {
51+
$OUTPUT_NAME += ".exe"
52+
}
53+
54+
Write-Host "Building for $OS/$ARCH..."
55+
56+
# Delete the output file if it already exists
57+
if (Test-Path -Path $OUTPUT_NAME) {
58+
Remove-Item -Path $OUTPUT_NAME -Force
59+
}
60+
61+
if ($env:EXTENSION_LANGUAGE -eq "dotnet") {
62+
# Set runtime identifier for .NET
63+
$RUNTIME = if ($OS -eq "windows") { "win-$ARCH" } elseif ($OS -eq "darwin") { "osx-$ARCH" } else { "linux-$ARCH" }
64+
$PROJECT_FILE = "$EXTENSION_ID_SAFE.csproj"
65+
66+
# Run dotnet publish for single file executable
67+
dotnet publish `
68+
-c Release `
69+
-r $RUNTIME `
70+
-o $OUTPUT_DIR `
71+
/p:PublishTrimmed=true `
72+
$PROJECT_FILE
73+
74+
if ($LASTEXITCODE -ne 0) {
75+
Write-Host "An error occurred while building for $OS/$ARCH"
76+
exit 1
77+
}
78+
79+
$EXPECTED_OUTPUT_NAME = $EXTENSION_ID_SAFE
80+
if ($OS -eq "windows") {
81+
$EXPECTED_OUTPUT_NAME += ".exe"
82+
}
83+
84+
Rename-Item -Path "$OUTPUT_DIR/$EXPECTED_OUTPUT_NAME" -NewName $OUTPUT_NAME
85+
} elseif ($env:EXTENSION_LANGUAGE -eq "javascript") {
86+
$ENTRY_FILE = "pkg-entry.js"
87+
$TARGET = "node16-$OS-x64"
88+
$EXPECTED_OUTPUT_NAME = "$EXTENSION_ID_SAFE-$OS-$ARCH"
89+
if ($OS -eq "windows") {
90+
$EXPECTED_OUTPUT_NAME += ".exe"
91+
}
92+
93+
Write-Host "Installing dependencies..."
94+
npm install
95+
96+
Write-Host "Building JavaScript extension for $OS/$ARCH..."
97+
pkg $ENTRY_FILE -o $OUTPUT_DIR/$EXPECTED_OUTPUT_NAME --targets $TARGET --config package.json
98+
99+
if ($LASTEXITCODE -ne 0) {
100+
Write-Host "An error occurred while building for $OS/$ARCH"
101+
exit 1
102+
}
103+
} elseif ($env:EXTENSION_LANGUAGE -eq "go") {
104+
# Set environment variables for Go build
105+
$env:GOOS = $OS
106+
$env:GOARCH = $ARCH
107+
108+
go build `
109+
-ldflags="-X '$APP_PATH.Version=$env:EXTENSION_VERSION' -X '$APP_PATH.Commit=$COMMIT' -X '$APP_PATH.BuildDate=$BUILD_DATE'" `
110+
-o $OUTPUT_NAME
111+
112+
if ($LASTEXITCODE -ne 0) {
113+
Write-Host "An error occurred while building for $OS/$ARCH"
114+
exit 1
115+
}
116+
} else {
117+
Write-Host "Error: Unsupported BUILD_TYPE '$env:BUILD_TYPE'. Use 'go' or 'dotnet'."
118+
exit 1
119+
}
120+
}
121+
122+
Write-Host "Build completed successfully!"
123+
Write-Host "Binaries are located in the $OUTPUT_DIR directory."
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/bin/bash
2+
3+
# Get the directory of the script
4+
EXTENSION_DIR="$(cd "$(dirname "$0")" && pwd)"
5+
6+
# Change to the script directory
7+
cd "$EXTENSION_DIR" || exit
8+
9+
# Create a safe version of EXTENSION_ID replacing dots with dashes
10+
EXTENSION_ID_SAFE="${EXTENSION_ID//./-}"
11+
12+
# Define output directory
13+
OUTPUT_DIR="${OUTPUT_DIR:-$EXTENSION_DIR/bin}"
14+
15+
# Create output and target directories if they don't exist
16+
mkdir -p "$OUTPUT_DIR"
17+
18+
# Get Git commit hash and build date
19+
COMMIT=$(git rev-parse HEAD)
20+
BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
21+
22+
# List of OS and architecture combinations
23+
if [ -n "$EXTENSION_PLATFORM" ]; then
24+
PLATFORMS=("$EXTENSION_PLATFORM")
25+
else
26+
PLATFORMS=(
27+
"windows/amd64"
28+
"windows/arm64"
29+
"darwin/amd64"
30+
"darwin/arm64"
31+
"linux/amd64"
32+
"linux/arm64"
33+
)
34+
fi
35+
36+
APP_PATH="github.com/azure/azure-dev/cli/azd/extensions/$EXTENSION_ID/internal/cmd"
37+
38+
# Check if the build type is specified
39+
if [ -z "$EXTENSION_LANGUAGE" ]; then
40+
echo "Error: EXTENSION_LANGUAGE environment variable is required (go or dotnet)"
41+
exit 1
42+
fi
43+
44+
# Loop through platforms and build
45+
for PLATFORM in "${PLATFORMS[@]}"; do
46+
OS=$(echo "$PLATFORM" | cut -d'/' -f1)
47+
ARCH=$(echo "$PLATFORM" | cut -d'/' -f2)
48+
49+
OUTPUT_NAME="$OUTPUT_DIR/$EXTENSION_ID_SAFE-$OS-$ARCH"
50+
51+
if [ "$OS" = "windows" ]; then
52+
OUTPUT_NAME+='.exe'
53+
fi
54+
55+
echo "Building for $OS/$ARCH..."
56+
57+
# Delete the output file if it already exists
58+
[ -f "$OUTPUT_NAME" ] && rm -f "$OUTPUT_NAME"
59+
60+
if [ "$EXTENSION_LANGUAGE" = "dotnet" ]; then
61+
# Set runtime identifier for .NET
62+
if [ "$OS" = "windows" ]; then
63+
RUNTIME="win-$ARCH"
64+
elif [ "$OS" = "darwin" ]; then
65+
RUNTIME="osx-$ARCH"
66+
else
67+
RUNTIME="linux-$ARCH"
68+
fi
69+
PROJECT_FILE="azd-extension.csproj"
70+
71+
# Run dotnet publish for single file executable
72+
dotnet publish \
73+
-c Release \
74+
-r "$RUNTIME" \
75+
-o "$OUTPUT_DIR" \
76+
/p:PublishTrimmed=true \
77+
"$PROJECT_FILE"
78+
79+
if [ $? -ne 0 ]; then
80+
echo "An error occurred while building for $OS/$ARCH"
81+
exit 1
82+
fi
83+
84+
EXPECTED_OUTPUT_NAME="$EXTENSION_ID_SAFE"
85+
if [ "$OS" = "windows" ]; then
86+
EXPECTED_OUTPUT_NAME+='.exe'
87+
fi
88+
89+
mv "$OUTPUT_DIR/$EXPECTED_OUTPUT_NAME" "$OUTPUT_NAME"
90+
elif [ "$EXTENSION_LANGUAGE" = "javascript" ]; then
91+
ENTRY_FILE="pkg-entry.js"
92+
TARGET="node16-$OS-x64"
93+
EXPECTED_OUTPUT_NAME="$EXTENSION_ID_SAFE-$OS-$ARCH"
94+
95+
if [ "$OS" = "windows" ]; then
96+
EXPECTED_OUTPUT_NAME+='.exe'
97+
fi
98+
99+
echo "Installing dependencies..."
100+
npm install
101+
102+
echo "Building JavaScript extension for $OS/$ARCH..."
103+
pkg "$ENTRY_FILE" --output "$OUTPUT_DIR/$EXPECTED_OUTPUT_NAME" --targets "$TARGET"
104+
105+
if [ $? -ne 0 ]; then
106+
echo "An error occurred while building for $OS/$ARCH"
107+
exit 1
108+
fi
109+
elif [ "$EXTENSION_LANGUAGE" = "go" ]; then
110+
# Set environment variables for Go build
111+
GOOS=$OS GOARCH=$ARCH go build \
112+
-ldflags="-X '$APP_PATH.Version=$EXTENSION_VERSION' -X '$APP_PATH.Commit=$COMMIT' -X '$APP_PATH.BuildDate=$BUILD_DATE'" \
113+
-o "$OUTPUT_NAME"
114+
115+
if [ $? -ne 0 ]; then
116+
echo "An error occurred while building for $OS/$ARCH"
117+
exit 1
118+
fi
119+
else
120+
echo "Error: Unsupported EXTENSION_LANGUAGE '$EXTENSION_LANGUAGE'. Use 'go' or 'dotnet'."
121+
exit 1
122+
fi
123+
done
124+
125+
echo "Build completed successfully!"
126+
echo "Binaries are located in the $OUTPUT_DIR directory."

0 commit comments

Comments
 (0)