diff --git a/auth_samples_astro/package-lock.json b/auth_samples_astro/package-lock.json index 7c791e9..893eb0c 100644 --- a/auth_samples_astro/package-lock.json +++ b/auth_samples_astro/package-lock.json @@ -646,12 +646,35 @@ "dev": true, "license": "MIT" }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -2003,7 +2026,6 @@ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -2131,7 +2153,6 @@ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2251,7 +2272,6 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-7.2.0.tgz", "integrity": "sha512-lLTYzx3fOvCmtwD3JVBLQcbORbIOW1/j0R+3IvJx/XKwMGrk7mFnF0BYSOeRiNw1qHUR5mdA6+hRnyvyDfqrWQ==", "license": "MIT", - "peer": true, "dependencies": { "@astrojs/compiler-rs": "^0.3.2", "@astrojs/internal-helpers": "0.10.2", @@ -2813,7 +2833,6 @@ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4025,7 +4044,6 @@ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -4557,7 +4575,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4870,7 +4887,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -5295,7 +5311,6 @@ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "devOptional": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/auth_samples_react/package-lock.json b/auth_samples_react/package-lock.json index 9a6891b..5762909 100644 --- a/auth_samples_react/package-lock.json +++ b/auth_samples_react/package-lock.json @@ -311,6 +311,43 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", diff --git a/distribution/scripts/package-template.mjs b/distribution/scripts/package-template.mjs index 423e272..8ad68f2 100644 --- a/distribution/scripts/package-template.mjs +++ b/distribution/scripts/package-template.mjs @@ -92,7 +92,11 @@ function generatedWorkflow(template) { cache: npm `, "Gradle Wrapper": " - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4\n with:\n distribution: temurin\n java-version: 17\n cache: gradle\n", - pip: " - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5\n with:\n python-version: '3.11'\n cache: pip\n", + pip: ` - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '${template.pythonVersion ?? "3.11"}' + cache: pip +`, "Go modules": " - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5\n with:\n go-version-file: go.mod\n cache: true\n", "Flutter pub": " - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2\n with:\n channel: stable\n cache: true\n", Composer: " - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5\n with:\n php-version: '8.3'\n tools: composer:v2\n coverage: none\n", diff --git a/distribution/scripts/package-template.test.mjs b/distribution/scripts/package-template.test.mjs index 49ab560..fa2abce 100644 --- a/distribution/scripts/package-template.test.mjs +++ b/distribution/scripts/package-template.test.mjs @@ -12,6 +12,7 @@ const manifest = loadCatalog(); const aiSaas = manifest.products.find((product) => product.id === "ai-saas"); const react = manifest.templates.find((template) => template.id === "react-vite"); const laravel = manifest.templates.find((template) => template.id === "laravel"); +const django = manifest.templates.find((template) => template.id === "django"); const repositoryRoot = resolve(import.meta.dirname, "../.."); const sliceFourSecurityFiles = new Map([ ["sveltekit", ["src/lib/server/oidc.ts"]], @@ -83,6 +84,18 @@ test("packages the AI SaaS product separately from the twenty framework satellit } }); +test("generates the declared runtime version in satellite CI", () => { + const temporary = mkdtempSync(resolve(tmpdir(), "tuurio-package-runtime-test-")); + try { + const output = resolve(temporary, "django"); + packageTemplate(django, { output, sourceSha: "7".repeat(40) }); + const workflow = readFileSync(resolve(output, ".github/workflows/verify.yml"), "utf8"); + assert.match(workflow, /python-version: '3\.12'/); + } finally { + rmSync(temporary, { recursive: true, force: true }); + } +}); + test("packages the six server starters with the reviewed authentication contract", () => { const temporary = mkdtempSync(resolve(tmpdir(), "tuurio-package-server-contract-test-")); try { diff --git a/distribution/scripts/validate-manifest.mjs b/distribution/scripts/validate-manifest.mjs index 94c98b9..eeba931 100644 --- a/distribution/scripts/validate-manifest.mjs +++ b/distribution/scripts/validate-manifest.mjs @@ -117,6 +117,25 @@ export const validateManifest = (manifest, { repositoryRoot = root } = {}) => { if (template.start !== undefined) { validateCommand(template.start, `${prefix}.start`, errors); } + if ( + template.nodeVersion !== undefined && + !/^[0-9]+(?:\.[0-9]+){0,2}$/.test(String(template.nodeVersion)) + ) { + errors.push(`${prefix}: nodeVersion must be a numeric version`); + } + if ( + template.pythonVersion !== undefined && + !/^[0-9]+(?:\.[0-9]+){1,2}$/.test(String(template.pythonVersion)) + ) { + errors.push(`${prefix}: pythonVersion must include major and minor numeric versions`); + } + if ( + template.packageManager === "pip" && + template.runtime?.startsWith("Python 3.12") && + String(template.pythonVersion) !== "3.12" + ) { + errors.push(`${prefix}: Python 3.12 runtime must generate CI with pythonVersion 3.12`); + } if (template.files !== undefined) { if (!Array.isArray(template.files) || template.files.length < 1) { errors.push(`${prefix}: files must contain at least one allow-listed path`); diff --git a/distribution/scripts/validate-manifest.test.mjs b/distribution/scripts/validate-manifest.test.mjs index fc5e914..5adc147 100644 --- a/distribution/scripts/validate-manifest.test.mjs +++ b/distribution/scripts/validate-manifest.test.mjs @@ -98,3 +98,11 @@ test("rejects a framework label that cannot render safely", () => { const { errors } = validateManifest(manifest, { repositoryRoot: root }); assert.ok(errors.some((error) => error.includes("framework must use 1-24 lowercase ASCII slug characters"))); }); + +test("requires Python 3.12 CI for templates that declare that runtime", () => { + const manifest = copy(); + const django = manifest.templates.find((template) => template.id === "django"); + django.pythonVersion = "3.11"; + const { errors } = validateManifest(manifest, { repositoryRoot: root }); + assert.ok(errors.some((error) => error.includes("Python 3.12 runtime must generate CI"))); +}); diff --git a/distribution/templates.yml b/distribution/templates.yml index 09cfb4e..2716d4a 100644 --- a/distribution/templates.yml +++ b/distribution/templates.yml @@ -324,6 +324,7 @@ templates: campaign: github_django framework: django runtime: Python 3.12+ + pythonVersion: "3.12" packageManager: pip start: python3 manage.py runserver 0.0.0.0:8000 kind: server @@ -341,6 +342,7 @@ templates: campaign: github_fastapi framework: fastapi runtime: Python 3.12+ + pythonVersion: "3.12" packageManager: pip start: uvicorn app.main:app --host 0.0.0.0 --port 8000 kind: server