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
21 changes: 21 additions & 0 deletions .github/workflows/node-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,30 @@ jobs:
- *checkout-repository
- &set-up-node-js
name: Set up Node.js
id: node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: ${{ matrix.node }}
cache: npm
cache-dependency-path: sdk/typescript/pnpm-lock.yaml
- &resolve-npm-cache
name: Resolve npm download cache
id: npm-cache
if: steps.node.outputs.cache-hit != 'true'
shell: bash
env:
PRIMARY_CACHE_KEY: ${{ steps.node.outputs.cache-primary-key }}
run: |
echo "path=$(npm config get cache)" >> "$GITHUB_OUTPUT"
echo "restore-prefix=${PRIMARY_CACHE_KEY%-*}-" >> "$GITHUB_OUTPUT"
- &restore-npm-cache
name: Restore previous npm downloads
if: steps.node.outputs.cache-hit != 'true'
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.npm-cache.outputs.path }}
key: ${{ steps.node.outputs.cache-primary-key }}
restore-keys: ${{ steps.npm-cache.outputs.restore-prefix }}
- *download-package-for-this-commit
- name: Inspect installed package
working-directory: sdk/typescript
Expand Down Expand Up @@ -556,6 +575,8 @@ jobs:
- *checkout-repository
- *set-up-python
- *set-up-node-js
- *resolve-npm-cache
- *restore-npm-cache
- *download-package-for-this-commit
- name: Inspect installed package
working-directory: sdk/typescript
Expand Down
2 changes: 1 addition & 1 deletion sdk/typescript/tests-ts/cli-skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe("CLI skill commands", () => {
);

test.each([0, 2])(
"combines inline, file, and directory context and cleans up after exit %s",
"combines inline, file, and directory context and cleans up after exit %d",
async (status) => {
const directory = await temporaryDirectory("validation-context-");
await mkdir(join(directory, "context", "nested"), { recursive: true });
Expand Down
4 changes: 2 additions & 2 deletions sdk/typescript/tests-ts/scan-skill-paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async function fixture() {
}

test.each([false, true])(
"disables cached skill files while preserving effective selection (profile=%s)",
"disables cached skill files while preserving effective selection (profile=%j)",
async (profile) => {
const { root, home, plugin, blocked } = await fixture();
const unrelated = {
Expand Down Expand Up @@ -117,7 +117,7 @@ test("leaves plugins outside the credential home and absent skills unchanged", a
});

test.each([false, true])(
"preserves malformed native skill settings (profile=%s)",
"preserves malformed native skill settings (profile=%j)",
async (profile) => {
const { home, plugin } = await fixture();
for (const skills of [
Expand Down
85 changes: 84 additions & 1 deletion sdk/typescript/tests-ts/skeleton.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { readFile } from "node:fs/promises";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "bun:test";
import { parse } from "smol-toml";
import {
Expand All @@ -10,9 +12,11 @@ import {
} from "../src/index.js";
import { main } from "../src/cli.js";
import { capture } from "./cli-fixtures.js";
import { bashCommand, runCommand } from "./support/shell.js";

interface WorkflowStep {
name?: string;
id?: string;
uses?: string;
with?: Record<string, unknown>;
run?: string;
Expand Down Expand Up @@ -206,6 +210,85 @@ describe("TypeScript package skeleton", () => {
}
});

test("reuses npm downloads after lockfile changes without skipping package installation", async () => {
const { jobs } = await workflow("node-ci.yml");
for (const name of ["compatibility", "windows-verify"]) {
const steps = jobs[name]!.steps!;
const setup = steps.findIndex(({ id }) => id === "node");
const resolver = steps.findIndex(({ id }) => id === "npm-cache");
const restore = steps.findIndex(({ uses }) =>
uses?.startsWith("actions/cache/restore@"),
);
const inspection = steps.findIndex(
({ name }) => name === "Inspect installed package",
);
expect(setup).toBeGreaterThanOrEqual(0);
expect(setup).toBeLessThan(resolver);
expect(resolver).toBeLessThan(restore);
expect(restore).toBeLessThan(inspection);
expect(steps[setup]?.with?.["cache"]).toBe("npm");
expect(steps[resolver]?.env).toEqual({
PRIMARY_CACHE_KEY: "${{ steps.node.outputs.cache-primary-key }}",
});
for (const index of [resolver, restore]) {
expect(steps[index]?.if).toBe("steps.node.outputs.cache-hit != 'true'");
}
expect(steps[restore]?.with).toEqual({
path: "${{ steps.npm-cache.outputs.path }}",
key: "${{ steps.node.outputs.cache-primary-key }}",
"restore-keys": "${{ steps.npm-cache.outputs.restore-prefix }}",
});
expect(steps[inspection]).not.toHaveProperty("if");
}
});

test("npm cache fallback shares lockfile versions within each platform and architecture", async () => {
const { jobs } = await workflow("node-ci.yml");
const script = jobs["compatibility"]!.steps!.find(
({ id }) => id === "npm-cache",
)!.run!;
const directory = await mkdtemp(join(tmpdir(), "npm-cache-workflow-"));
try {
for (const platform of ["Windows-x64", "Windows-arm64", "Linux-x64"]) {
for (const hash of ["a".repeat(64), "b".repeat(64)]) {
const cachePath = platform.startsWith("Windows")
? "C:\\npm cache"
: "/tmp/npm cache";
const output = join(directory, "output.txt");
await rm(output, { force: true });
const result = await runCommand(
bashCommand(),
[
"-e",
"-o",
"pipefail",
"-c",
`npm() { test "$*" = "config get cache" && printf '%s\\n' "$TEST_NPM_CACHE"; }\n${script}`,
],
{
cwd: directory,
env: {
...process.env,
PRIMARY_CACHE_KEY: `node-cache-${platform}-npm-${hash}`,
GITHUB_OUTPUT: "output.txt",
TEST_NPM_CACHE: cachePath,
},
},
);
expect(result.status, result.stderr).toBe(0);
expect(
(await readFile(output, "utf8")).trim().split(/\r?\n/u),
).toEqual([
`path=${cachePath}`,
`restore-prefix=node-cache-${platform}-npm-`,
]);
}
}
} finally {
await rm(directory, { recursive: true, force: true });
}
});

test("randomizes tests and keeps the default and Windows CI timeouts", async () => {
const packageJson = JSON.parse(
await readFile(new URL("../package.json", import.meta.url), "utf8"),
Expand Down
Loading