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
1 change: 1 addition & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ jobs:
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
deno run -A scripts/ci/prepare-rc-build.ts
deno task build:npm
scripts/ci/publish-npm-packages.sh rc-publish

Expand Down
6 changes: 3 additions & 3 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "veryfront",
"version": "0.1.1229",
"version": "0.1.1230-rc",
Comment thread
kwakayama marked this conversation as resolved.
"license": "Apache-2.0",
"nodeModulesDir": "auto",
"minimumDependencyAge": {
Expand Down Expand Up @@ -494,7 +494,7 @@
"build:storybook": "npm --prefix storybook run build-storybook",
"storybook:check": "deno test --no-lock --config=scripts/test.deno.json --no-check --allow-read scripts/storybook/storybook-workbench.test.ts",
"lint": "DENO_NO_PACKAGE_JSON=1 deno lint && deno lint --config=scripts/test.deno.json scripts/test/ scripts/build/dnt-polyfill.ts scripts/build/dnt-polyfill.test.ts scripts/build/npm-package-metadata.test.ts scripts/build/prepare-framework-sources.test.ts && deno lint --config=scripts/codemods/deno.json scripts/codemods/",
"lint:ci": "deno task lint && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:chat-ratchets && deno task lint:esm-sh-codemod && deno task lint:test-typecheck && deno task lint:cwd-relative-test-reads && deno task storybook:check && deno task docs:api-reference:check && deno task docs:errors:check && deno test --frozen --config=scripts/test.deno.json --no-check --allow-read --allow-write scripts/ci/setup-deno-workflow.test.ts scripts/build/generated-artifact-checks.test.ts",
"lint:ci": "deno task lint && deno task lint:core-deps && deno task lint:cross-runtime-jsr && deno task lint:dependency-boundaries && deno task lint:module-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task lint:ban-test-only && deno task lint:sanitizer-baseline && deno task lint:skipped-tests && deno task lint:chat-ratchets && deno task lint:esm-sh-codemod && deno task lint:test-typecheck && deno task lint:cwd-relative-test-reads && deno task storybook:check && deno task docs:api-reference:check && deno task docs:errors:check && deno test --frozen --config=scripts/test.deno.json --no-check --allow-read --allow-write scripts/ci/setup-deno-workflow.test.ts scripts/ci/prepare-rc-build.test.ts scripts/build/generated-artifact-checks.test.ts",
"fmt": "deno fmt src/ cli/ react/ templates/ && deno fmt --config=scripts/test.deno.json scripts/test/ scripts/build/dnt-polyfill.ts scripts/build/dnt-polyfill.test.ts scripts/build/prepare-framework-sources.test.ts && deno fmt --config=scripts/codemods/deno.json scripts/codemods/",
"fmt:check": "deno fmt --check src/ cli/ react/ templates/ && deno fmt --check --config=scripts/test.deno.json scripts/test/ scripts/build/dnt-polyfill.ts scripts/build/dnt-polyfill.test.ts scripts/build/prepare-framework-sources.test.ts && deno fmt --check --config=scripts/codemods/deno.json scripts/codemods/",
"typecheck": "deno task generate:manifests:check && deno check src/index.ts cli/main.ts src/server/index.ts src/routing/api/index.ts src/rendering/index.ts src/platform/index.ts src/platform/adapters/index.ts src/build/index.ts src/build/production-build/index.ts src/transforms/index.ts src/config/index.ts src/utils/index.ts src/data/index.ts src/security/index.ts src/middleware/index.ts src/server/handlers/dev/index.ts src/server/handlers/request/api/index.ts src/rendering/cache/index.ts src/rendering/cache/stores/index.ts src/rendering/rsc/actions/index.ts src/html/index.ts src/html/hydration-script-builder/runtime/main.ts src/modules/index.ts src/proxy/main.ts src/react/components/ui/index.ts src/chat/index.ts src/markdown/index.ts src/mdx/index.ts src/fs/index.ts src/oauth/index.ts src/agent/index.ts src/agent/service/route-export.check.ts src/eval/index.ts src/tool/index.ts src/workflow/index.ts src/prompt/index.ts src/resource/index.ts src/runs/index.ts src/mcp/index.ts src/provider/index.ts",
Expand Down Expand Up @@ -540,7 +540,7 @@
"lint:sanitizer-baseline": "deno run --allow-read scripts/lint/check-sanitizer-baseline.ts",
"lint:skipped-tests": "deno run --allow-read scripts/lint/check-skipped-tests-baseline.ts",
"lint:cwd-relative-test-reads": "deno run --allow-read scripts/lint/audit-cwd-relative-test-reads.ts",
"test:scripts": "deno test --config=scripts/test.deno.json --no-check --allow-read --allow-write --allow-run scripts/ci/publish-npm-packages.test.ts scripts/ci/setup-deno-workflow.test.ts scripts/build/compile-binary.test.ts scripts/build/dnt-polyfill.test.ts scripts/build/generate-sbom.test.ts scripts/build/generated-artifact-checks.test.ts scripts/build/npm-dependency-sources.test.ts scripts/build/npm-extension-package-metadata.test.ts scripts/build/npm-package-metadata.test.ts scripts/build/npm-react-shims.test.ts scripts/build/npm-runtime-helper-contract.test.ts scripts/build/prepare-framework-sources.test.ts scripts/docs/docs-coverage.test.ts scripts/docs/generate-api-reference.test.ts scripts/docs/guide-validation.test.ts scripts/lint/audit-core-deps.test.ts scripts/lint/audit-cwd-relative-test-reads.test.ts scripts/lint/audit-cross-runtime-jsr.test.ts scripts/lint/audit-dependency-boundaries.test.ts scripts/lint/audit-extension-capabilities.test.ts scripts/lint/audit-extension-contracts.test.ts scripts/lint/audit-deps.test.ts scripts/lint/check-module-boundaries.test.ts scripts/lint/lint-config.test.ts scripts/lint/ban-test-only.test.ts scripts/lint/check-sanitizer-baseline.test.ts scripts/lint/check-skipped-tests-baseline.test.ts scripts/lint/check-test-typecheck-baseline.test.ts scripts/lint/check-coverage.test.ts scripts/security/audit-npm.test.ts scripts/security/submit-dependency-snapshot.test.ts scripts/test/template-runtime-e2e.test.ts && deno task test:tool-search-live",
"test:scripts": "deno test --config=scripts/test.deno.json --no-check --allow-read --allow-write --allow-run scripts/ci/prepare-rc-build.test.ts scripts/ci/publish-npm-packages.test.ts scripts/ci/setup-deno-workflow.test.ts scripts/build/compile-binary.test.ts scripts/build/dnt-polyfill.test.ts scripts/build/generate-sbom.test.ts scripts/build/generated-artifact-checks.test.ts scripts/build/npm-dependency-sources.test.ts scripts/build/npm-extension-package-metadata.test.ts scripts/build/npm-package-metadata.test.ts scripts/build/npm-react-shims.test.ts scripts/build/npm-runtime-helper-contract.test.ts scripts/build/prepare-framework-sources.test.ts scripts/docs/docs-coverage.test.ts scripts/docs/generate-api-reference.test.ts scripts/docs/guide-validation.test.ts scripts/lint/audit-core-deps.test.ts scripts/lint/audit-cwd-relative-test-reads.test.ts scripts/lint/audit-cross-runtime-jsr.test.ts scripts/lint/audit-dependency-boundaries.test.ts scripts/lint/audit-extension-capabilities.test.ts scripts/lint/audit-extension-contracts.test.ts scripts/lint/audit-deps.test.ts scripts/lint/check-module-boundaries.test.ts scripts/lint/lint-config.test.ts scripts/lint/ban-test-only.test.ts scripts/lint/check-sanitizer-baseline.test.ts scripts/lint/check-skipped-tests-baseline.test.ts scripts/lint/check-test-typecheck-baseline.test.ts scripts/lint/check-coverage.test.ts scripts/security/audit-npm.test.ts scripts/security/submit-dependency-snapshot.test.ts scripts/test/template-runtime-e2e.test.ts && deno task test:tool-search-live",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"test:sentry-runtime-packages": "deno test --config=scripts/test.deno.json --no-check --no-lock --allow-read --allow-write --allow-run --allow-env=DENO_DIR,HOME,XDG_CACHE_HOME,LOCALAPPDATA,USERPROFILE scripts/build/sentry-runtime-packages.test.ts",
"test:tool-search-live": "VF_DISABLE_LRU_INTERVAL=1 deno test --no-check -A tests/agent/verify-tool-search-live.test.ts",
"test:cross-runtime": "deno run --allow-all src/platform/compat/cross-runtime.test.ts",
Expand Down
87 changes: 87 additions & 0 deletions scripts/ci/prepare-rc-build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { prepareRcBuildVersion } from "./prepare-rc-build.ts";

async function createVersionFixture(
manifestVersion = "0.1.1230-rc",
sourceVersion = manifestVersion,
): Promise<string> {
const rootDir = await Deno.makeTempDir();
await Deno.mkdir(`${rootDir}/src/utils`, { recursive: true });
await Deno.writeTextFile(
`${rootDir}/deno.json`,
JSON.stringify({
name: "veryfront",
version: manifestVersion,
tasks: { "build:npm": "fixture" },
}, null, 2) + "\n",
);
await Deno.writeTextFile(
`${rootDir}/src/utils/version-constant.ts`,
`// Keep in sync with deno.json version.\nexport const VERSION = "${sourceVersion}";\n`,
);
return rootDir;
}

describe("RC build version preparation", () => {
it("injects the published RC version into the manifest and source constant", async () => {
const rootDir = await createVersionFixture();

try {
await prepareRcBuildVersion({
rootDir,
version: "0.1.1230-rc.456",
});

const manifest = JSON.parse(await Deno.readTextFile(`${rootDir}/deno.json`));
assertEquals(manifest.version, "0.1.1230-rc.456");
assertEquals(manifest.tasks["build:npm"], "fixture");
assertEquals(
await Deno.readTextFile(`${rootDir}/src/utils/version-constant.ts`),
'// Keep in sync with deno.json version.\nexport const VERSION = "0.1.1230-rc.456";\n',
);
} finally {
await Deno.remove(rootDir, { recursive: true });
}
});

it("rejects a publish version that is not the manifest prerelease plus a run number", async () => {
const rootDir = await createVersionFixture();

try {
await assertRejects(
() =>
prepareRcBuildVersion({
rootDir,
version: "0.1.1231-rc.456",
}),
Error,
"must extend 0.1.1230-rc with a numeric run number",
);
assertEquals(
JSON.parse(await Deno.readTextFile(`${rootDir}/deno.json`)).version,
"0.1.1230-rc",
);
} finally {
await Deno.remove(rootDir, { recursive: true });
}
});

it("rejects source and manifest versions that are already out of sync", async () => {
const rootDir = await createVersionFixture("0.1.1230-rc", "0.1.1229-rc");

try {
await assertRejects(
() =>
prepareRcBuildVersion({
rootDir,
version: "0.1.1230-rc.456",
}),
Error,
"does not match deno.json version",
);
} finally {
await Deno.remove(rootDir, { recursive: true });
}
});
});
89 changes: 89 additions & 0 deletions scripts/ci/prepare-rc-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { join } from "#std/path";

type PrepareRcBuildVersionOptions = {
rootDir?: string;
version: string;
};

const VERSION_CONSTANT_PATTERN =
/^export const VERSION = "([^"]+)";$/gm;

/** Inject the CI-generated RC version before npm build artifacts are created. */
export async function prepareRcBuildVersion(
options: PrepareRcBuildVersionOptions,
): Promise<void> {
const rootDir = options.rootDir ?? Deno.cwd();
const manifestPath = join(rootDir, "deno.json");
const versionConstantPath = join(
rootDir,
"src/utils/version-constant.ts",
);
const manifestSource = await Deno.readTextFile(manifestPath);
const manifest = JSON.parse(manifestSource) as { version?: unknown };
const baseVersion = manifest.version;

if (typeof baseVersion !== "string" || baseVersion.length === 0) {
throw new Error("deno.json must define a non-empty string version");
}
if (!/^\d+\.\d+\.\d+-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*$/.test(baseVersion)) {
throw new Error(`deno.json version ${baseVersion} is not a prerelease version`);
}

const versionPrefix = `${baseVersion}.`;
const runNumber = options.version.startsWith(versionPrefix)
? options.version.slice(versionPrefix.length)
: "";
if (!/^[1-9]\d*$/.test(runNumber)) {
throw new Error(
`RC build version ${options.version} must extend ${baseVersion} with a numeric run number`,
);
}

const versionConstantSource = await Deno.readTextFile(versionConstantPath);
const versionConstantMatches = [...versionConstantSource.matchAll(
VERSION_CONSTANT_PATTERN,
)];
if (versionConstantMatches.length !== 1) {
throw new Error(
"src/utils/version-constant.ts must contain exactly one exported VERSION constant",
);
}
if (versionConstantMatches[0][1] !== baseVersion) {
throw new Error(
`src/utils/version-constant.ts version ${versionConstantMatches[0][1]} does not match deno.json version ${baseVersion}`,
);
}

const manifestVersionPattern =
/^(\s*"version"\s*:\s*)"([^"]+)"(,?\s*)$/gm;
const manifestVersionMatches = [...manifestSource.matchAll(
manifestVersionPattern,
)];
if (
manifestVersionMatches.length !== 1 ||
manifestVersionMatches[0][2] !== baseVersion
) {
throw new Error("deno.json must contain exactly one matching version field");
}

const nextManifestSource = manifestSource.replace(
manifestVersionPattern,
`$1"${options.version}"$3`,
);
const nextVersionConstantSource = versionConstantSource.replace(
VERSION_CONSTANT_PATTERN,
`export const VERSION = "${options.version}";`,
);

await Deno.writeTextFile(manifestPath, nextManifestSource);
await Deno.writeTextFile(versionConstantPath, nextVersionConstantSource);
}

if (import.meta.main) {
const version = Deno.env.get("VERSION");
if (!version) {
throw new Error("VERSION must be set for RC build preparation");
}
await prepareRcBuildVersion({ version });
console.log(`Prepared npm source artifacts for ${version}`);
}

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/security/repository-hardening.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ describe("repository hardening", () => {
assert(workflow.includes("scripts/ci/publish-npm-packages.sh rc-publish"));
assert(workflow.includes("scripts/ci/publish-npm-packages.sh preflight"));
assert(workflow.includes("scripts/ci/publish-npm-packages.sh release-publish"));
assert(workflow.includes("deno run -A scripts/ci/prepare-rc-build.ts"));

const prerelease = jobBlock(workflow, "prerelease");
assert(
prerelease.indexOf("deno run -A scripts/ci/prepare-rc-build.ts") <
prerelease.indexOf("deno task build:npm"),
);
Comment on lines +95 to +101

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require preparation in the prerelease job.

The global assertion at Line 95 permits the command in a different job. If prerelease does not contain the command, its index is -1, and the comparison at Lines 99-100 still passes. Store the prerelease command index and assert that it is non-negative before comparing it with build:npm.

Proposed fix
-    assert(workflow.includes("deno run -A scripts/ci/prepare-rc-build.ts"));
-
     const prerelease = jobBlock(workflow, "prerelease");
+    const prepareIndex = prerelease.indexOf(
+      "deno run -A scripts/ci/prepare-rc-build.ts",
+    );
+    assert(prepareIndex >= 0, "expected prerelease to prepare the RC version");
     assert(
-      prerelease.indexOf("deno run -A scripts/ci/prepare-rc-build.ts") <
+      prepareIndex <
         prerelease.indexOf("deno task build:npm"),
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert(workflow.includes("deno run -A scripts/ci/prepare-rc-build.ts"));
const prerelease = jobBlock(workflow, "prerelease");
assert(
prerelease.indexOf("deno run -A scripts/ci/prepare-rc-build.ts") <
prerelease.indexOf("deno task build:npm"),
);
const prerelease = jobBlock(workflow, "prerelease");
const prepareIndex = prerelease.indexOf(
"deno run -A scripts/ci/prepare-rc-build.ts",
);
assert(prepareIndex >= 0, "expected prerelease to prepare the RC version");
assert(
prepareIndex <
prerelease.indexOf("deno task build:npm"),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/security/repository-hardening.test.ts` around lines 95 - 101, Update the
prerelease assertions in repository-hardening tests to store the preparation
command index from the prerelease job block, assert it is non-negative, then
verify it precedes the build:npm command. Do not rely on the global workflow
assertion alone.


assertEquals(publishScript.includes("NPM_TOKEN"), false);
assertEquals(publishScript.includes("NODE_AUTH_TOKEN"), false);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/version-constant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Keep in sync with deno.json version.
// scripts/release.ts updates this constant during releases.
/** Shared version value. */
export const VERSION = "0.1.1229";
export const VERSION = "0.1.1230-rc";