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
190 changes: 190 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,193 @@ jobs:
permissions:
contents: write
pull-requests: write

publish-npm:
if: needs.version-and-tag.outputs.is-prerelease == 'false'
needs: [ get-configs, version-and-tag, long-running-tests ]
runs-on: ubuntu-latest
environment: release-npm
permissions:
contents: read
id-token: write
env:
PACKAGE_NAME: '@aws/cloudformation-languageserver'
RELEASE_VERSION: ${{ needs.version-and-tag.outputs.version }}
steps:
- name: Download linux-x64 bundle
uses: actions/download-artifact@v8
with:
pattern: '*-linux-x64-node*'
path: artifacts

- name: Locate bundle
id: bundle
run: |
set -euo pipefail
mapfile -t bundles < <(find artifacts -mindepth 1 -maxdepth 1 -type d)
if [ "${#bundles[@]}" -ne 1 ]; then
echo "::error::Expected exactly one linux-x64 bundle artifact, found ${#bundles[@]}: ${bundles[*]}"
exit 1
fi
echo "Publishing from ${bundles[0]}"
echo "dir=${bundles[0]}" >> "$GITHUB_OUTPUT"

- uses: actions/setup-node@v6
with:
node-version: ${{ needs.get-configs.outputs.node-version }}
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false

- name: Upgrade npm for Trusted Publishing
run: |
npm install -g npm@11
echo "npm version: $(npm --version)"

- name: Match version with release tag
working-directory: ${{ steps.bundle.outputs.dir }}
run: |
set -euo pipefail
bundle_version="$(jq -r .version package.json)"
if [ "$bundle_version" != "$RELEASE_VERSION" ]; then
echo "::error::Bundle version ($bundle_version) does not match release version ($RELEASE_VERSION)."
exit 1
fi
echo "Bundle version $bundle_version matches release version."

- name: Confirm tarball contents
working-directory: ${{ steps.bundle.outputs.dir }}
run: |
set -euo pipefail

pack_json="$(mktemp)"
npm pack --dry-run --json > "$pack_json"

echo "Packed file list:"
jq -r '.[0].files[].path' "$pack_json"

required=(
"package.json"
"cfn-lsp-server-standalone.js"
"pyodide-worker.js"
"guard_bg.wasm"
"vendor/cfn-guard/index.js"
"README.md"
"LICENSE"
"NOTICE"
"THIRD-PARTY-LICENSES.txt"
)
required_prefixes=(
"assets/wheels/"
)
forbidden_prefixes=(
"node_modules/"
"bin/"
)

failed=0
for want in "${required[@]}"; do
if ! jq -e --arg f "$want" '.[0].files[] | select(.path == $f)' "$pack_json" >/dev/null; then
echo "::error::expected file missing from npm tarball: $want"
failed=1
fi
done
for prefix in "${required_prefixes[@]}"; do
if ! jq -e --arg p "$prefix" '.[0].files[] | select(.path | startswith($p))' "$pack_json" >/dev/null; then
echo "::error::expected files missing from npm tarball: $prefix*"
failed=1
fi
done
for prefix in "${forbidden_prefixes[@]}"; do
if jq -e --arg p "$prefix" '.[0].files[] | select(.path | startswith($p))' "$pack_json" >/dev/null; then
echo "::error::platform-specific files must not be in the npm tarball: $prefix*"
failed=1
fi
done

rm -f "$pack_json"

if [ "$failed" -ne 0 ]; then
echo "::error::tarball content verification failed"
exit 1
fi
echo "All required files are present in the tarball."

- name: Publish to NPM
working-directory: ${{ steps.bundle.outputs.dir }}
run: |
set -euo pipefail

release_url="https://registry.npmjs.org/${PACKAGE_NAME/\//%2F}/$RELEASE_VERSION"
registry_response="$(mktemp)"
if ! http_status="$(curl --silent --show-error --location \
--output "$registry_response" --write-out '%{http_code}' "$release_url")"; then
rm -f "$registry_response"
echo "::error::Failed to check NPM for release $RELEASE_VERSION."
exit 1
fi
case "$http_status" in
200)
rm -f "$registry_response"
echo "NPM release $RELEASE_VERSION already exists; skipping publication."
exit 0
;;
404) rm -f "$registry_response" ;;
*)
rm -f "$registry_response"
echo "::error::NPM existence check returned HTTP $http_status; refusing to publish."
exit 1
;;
esac

npm publish --access public --provenance

verify-publish-npm:
if: needs.version-and-tag.outputs.is-prerelease == 'false'
needs: [ get-configs, version-and-tag, publish-npm ]
runs-on: ubuntu-latest
env:
PACKAGE_NAME: '@aws/cloudformation-languageserver'
RELEASE_VERSION: ${{ needs.version-and-tag.outputs.version }}
steps:
- uses: actions/setup-node@v6
with:
node-version: ${{ needs.get-configs.outputs.node-version }}
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false

- name: Verify published package
run: |
set -euo pipefail

verify() {
local tmp
tmp="$(mktemp -d)"
(
cd "$tmp" \
&& npm init -y >/dev/null \
&& npm install "$PACKAGE_NAME@$RELEASE_VERSION" \
&& npm audit signatures \
&& test -x node_modules/.bin/cloudformation-languageserver
) || return 1

local predicate
predicate=$(npm view "$PACKAGE_NAME@$RELEASE_VERSION" dist.attestations.provenance.predicateType 2>/dev/null || true)
if [ -z "$predicate" ]; then
echo "no provenance attestation found for $PACKAGE_NAME@$RELEASE_VERSION"
return 1
fi
echo "provenance attestation predicateType: $predicate"
}

max_attempts=60
for attempt in $(seq 1 "$max_attempts"); do
echo "attempt $attempt/$max_attempts: verifying $PACKAGE_NAME@$RELEASE_VERSION"
if verify; then
echo "Published package installed, signatures audited, bin linked, and provenance verified."
exit 0
fi
sleep 60
done

echo "::error::Timed out waiting for $PACKAGE_NAME@$RELEASE_VERSION to verify against the npm registry"
exit 1
5 changes: 3 additions & 2 deletions .kiro/skills/cfn-lsp-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ These constraints apply to ALL changes in this repository:
types, or response/return types. Additive changes only — breaking the wire contract breaks the already-shipped VS Code
and JetBrains clients.
- **Cross-platform** — All changes must work on macOS, Linux, and Windows. The DataStore layer in particular has two
persisted backends (LMDB on macOS / Linux, encrypted file store on Windows or when the `FileDb` feature flag is on);
any persistence change must work against both. See `architecture.md`.
persisted backends (LMDB on macOS / Linux, encrypted file store on Windows, when the `FileDb` feature flag is on, or
when the `lmdb` native module cannot be loaded on the host); any persistence change must work against both. See
`architecture.md`.
- **No database locking** — Never hold long locks on LMDB or other shared resources; multiple concurrent LSP
connections may exist.
- **Performance** — Handlers must respond quickly; avoid blocking the event loop or doing synchronous I/O in request
Expand Down
28 changes: 16 additions & 12 deletions .kiro/steering/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,22 @@ CloudFormation resource type schemas.
Persistent and in-memory key-value storage. The `DataStore` interface (`src/datastore/DataStore.ts`) has **three
implementations** selected at runtime:

| Implementation | Module | Activation |
|--------------------------|--------------------------------|------------------------------------------------------|
| **LMDB store** (default) | `src/datastore/lmdb/` | All platforms by default (when not Windows / fileDb) |
| **File store** | `src/datastore/file/` | Windows OR `FileDb` feature flag enabled |
| **Memory store** | `src/datastore/MemoryStore.ts` | All non-persisted stores (e.g. `private_schemas`) |

`MultiDataStoreFactoryProvider` (`src/datastore/DataStore.ts`) chooses LMDB vs File at startup based on platform and
feature flag, and pairs whichever persisted store is selected with `MemoryStoreFactory` for in-memory stores.

- LMDB is the default persisted store on macOS / Linux. Database directory: `<storage-root>/lmdb/v5/`.
- File store is the encrypted-file alternative used on Windows or when LMDB is disabled. Database directory:
`<storage-root>/filedb/v3/`. One `.enc` file per key via `KeyedFileStore`.
| Implementation | Module | Activation |
|--------------------------|--------------------------------|---------------------------------------------------------------------|
| **LMDB store** (default) | `src/datastore/lmdb/` | macOS / Linux when the `lmdb` native module loads and `FileDb` is off |
| **File store** | `src/datastore/file/` | Windows, `FileDb` feature flag enabled, OR `lmdb` fails to load |
| **Memory store** | `src/datastore/MemoryStore.ts` | All non-persisted stores (e.g. `private_schemas`) |

`MultiDataStoreFactoryProvider` (`src/datastore/DataStore.ts`) chooses LMDB vs File at startup based on platform,
feature flag, and whether the `lmdb` native module can be loaded (probed via `src/datastore/lmdb/LMDBModule.ts`,
which is also the only place that loads `lmdb` at runtime), and pairs whichever persisted store is selected with
`MemoryStoreFactory` for in-memory stores.

- LMDB is the default persisted store on macOS / Linux. Database directory: `<storage-root>/lmdb/v7/`.
- File store is the encrypted-file alternative used on Windows, when LMDB is disabled, or when the `lmdb` native
addon cannot be loaded on the host (for example a glibc older than the prebuild requires); the fallback is logged
and counted under the `DataStore` telemetry scope as `lmdb.unavailable`. Database directory:
`<storage-root>/filedb/v4/`. One `.enc` file per key via `KeyedFileStore`.
- Memory store is used for `StoreName` values not in `PersistedStores` (currently `private_schemas`), so they are
loaded fresh each session.

Expand Down
4 changes: 2 additions & 2 deletions .kiro/steering/structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ These wrap implementation classes and are registered in `CfnServer.ts`:

- `src/datastore/` — Pluggable `DataStore` interface with three implementations:
- `src/datastore/lmdb/` — Default persisted store on macOS / Linux
- `src/datastore/file/` — Encrypted-file-per-key store; default on Windows or when `FileDb`
feature flag is enabled
- `src/datastore/file/` — Encrypted-file-per-key store; default on Windows, when the `FileDb`
feature flag is enabled, or when the `lmdb` native module cannot be loaded on the host
- `src/datastore/MemoryStore.ts` — In-memory, used for non-persisted stores
- `src/utils/Storage.ts` — Resolves platform-specific storage root

Expand Down
14 changes: 14 additions & 0 deletions INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ This guide covers installing and configuring the CloudFormation Language Server

1. [Node.js](https://nodejs.org/en) 24.x (version 24.19.0 or newer) for standard builds. Use Node.js 18.x only with the legacy Linux glibc 2.28 builds listed below.

## Install from npm

Production releases are published to npm as [`@aws/cloudformation-languageserver`](https://www.npmjs.com/package/@aws/cloudformation-languageserver) (Node.js 20 or newer):

```bash
npm install -g @aws/cloudformation-languageserver
cloudformation-languageserver --stdio
```

The package installs a `cloudformation-languageserver` executable. When installed this way, replace
`node /path/to/install-location/cfn-lsp-server-standalone.js` in the client configurations below with
`cloudformation-languageserver`. The npm package does not include the `cfn-init` binary or the legacy glibc 2.28
builds; use the GitHub Releases downloads for those.

## Download

Download the latest release for your platform from [GitHub Releases](https://github.com/aws-cloudformation/cloudformation-languageserver/releases/latest).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ The server implements the Language Server Protocol (LSP) to enable code editors

## Installation

For standalone editors like Neovim, Emacs, Helix, or Sublime Text, see the [Installation Guide](INSTALLATION.md).
For standalone editors like Neovim, Emacs, Helix, or Sublime Text, install from npm with `npm install -g @aws/cloudformation-languageserver` or download a platform build from GitHub Releases; see the [Installation Guide](INSTALLATION.md).

For [JetBrains](https://plugins.jetbrains.com/plugin/11349-aws-toolkit) and [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-toolkit-vscode), the language server is bundled with the ***AWS Toolkit*** extension.

Expand Down
Loading
Loading