mirror of
https://github.com/hanzoai/pack.git
synced 2026-09-30 23:43:13 +00:00
fix(node): react router spa detection (#604)
* chore: update react router to v8 and modernize node infrastructure - Bump React Router to v8.0.0 and update associated dependencies. - Upgrade Node.js base image to v24. - Simplify TSConfig and Vite configuration by utilizing native TSConfig path resolution. - Add .env to gitignore. - Ensure consistent scripting and environment settings across SPA and SSR examples. Generated-by: aiautocommit * feat(node): detect React Router SPA mode from config Generated-by: aiautocommit * test(node): improve React Router SPA detection and handling Generated-by: aiautocommit * chore(deps): update mise.dev.lock Generated-by: aiautocommit * docs(node): clarify SPA detection limitation and manual override Generated-by: aiautocommit * test(node): bind React Router preview server to IPv4 in SPA example - Force preview host to 127.0.0.1 in Vite config and generation script. - Prevent connection mismatch issues between prerender requests and the preview server. Generated-by: aiautocommit
This commit is contained in:
@@ -9,14 +9,32 @@ import (
|
||||
|
||||
const (
|
||||
DefaultReactRouterOutputDirectory = "build/client/"
|
||||
DefaultReactRouterStartCommand = "react-router-serve ./build/server/index.js"
|
||||
ReactRouterConfigJS = "react-router.config.js"
|
||||
ReactRouterConfigTS = "react-router.config.ts"
|
||||
)
|
||||
|
||||
var reactRouterSPAConfigRegex = regexp.MustCompile(`(?m)(?:^|[{,])\s*['"]?ssr['"]?\s*:\s*false\b`)
|
||||
|
||||
func (p *NodeProvider) getReactRouterConfigFileContents(ctx *generate.GenerateContext) string {
|
||||
_, contents, _ := ctx.App.ReadFirstFileOf(ReactRouterConfigJS, ReactRouterConfigTS)
|
||||
return contents
|
||||
}
|
||||
|
||||
// SPA mode requires an explicit opt-out because React Router enables SSR by default.
|
||||
func (p *NodeProvider) isReactRouterSPA(ctx *generate.GenerateContext) bool {
|
||||
if !p.isReactRouter(ctx) {
|
||||
return false
|
||||
}
|
||||
|
||||
configContent := p.getReactRouterConfigFileContents(ctx)
|
||||
return reactRouterSPAConfigRegex.MatchString(configContent)
|
||||
}
|
||||
|
||||
// getReactRouterOutputDirectory attempts to read the output directory from react-router.config.{ts,js} by extracting
|
||||
// the buildDirectory option in the config object.
|
||||
func (p *NodeProvider) getReactRouterOutputDirectory(ctx *generate.GenerateContext) string {
|
||||
_, configContent, _ := ctx.App.ReadFirstFileOf(ReactRouterConfigJS, ReactRouterConfigTS)
|
||||
configContent := p.getReactRouterConfigFileContents(ctx)
|
||||
|
||||
if configContent != "" {
|
||||
// TODO this field can be an expression `buildDirectory: "build/" + process.env.NODE_ENV,` so we should tighten
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReactRouterSPAConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
isSPA bool
|
||||
}{
|
||||
{
|
||||
name: "multiline config",
|
||||
config: "export default {\n ssr: false,\n} satisfies Config;",
|
||||
isSPA: true,
|
||||
},
|
||||
{
|
||||
name: "inline config",
|
||||
config: "export default { ssr: false } satisfies Config;",
|
||||
isSPA: true,
|
||||
},
|
||||
{
|
||||
name: "quoted property",
|
||||
config: "export default { 'ssr': false } satisfies Config;",
|
||||
isSPA: true,
|
||||
},
|
||||
{
|
||||
name: "server rendering enabled",
|
||||
config: "export default { ssr: true } satisfies Config;",
|
||||
isSPA: false,
|
||||
},
|
||||
{
|
||||
name: "server rendering omitted",
|
||||
config: "export default {} satisfies Config;",
|
||||
isSPA: false,
|
||||
},
|
||||
{
|
||||
name: "dynamic server rendering",
|
||||
config: "export default { ssr: process.env.SSR === 'true' } satisfies Config;",
|
||||
isSPA: false,
|
||||
},
|
||||
{
|
||||
name: "commented setting",
|
||||
config: "export default {\n // ssr: false,\n} satisfies Config;",
|
||||
isSPA: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.isSPA, reactRouterSPAConfigRegex.MatchString(tt.config))
|
||||
})
|
||||
}
|
||||
}
|
||||
+26
-13
@@ -37,39 +37,49 @@ func (p *NodeProvider) isSPA(ctx *generate.GenerateContext) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if p.isReactRouter(ctx) {
|
||||
return p.isReactRouterSPA(ctx) && p.getOutputDirectory(ctx) != ""
|
||||
}
|
||||
|
||||
isVite := p.isVite(ctx)
|
||||
isAstro := p.isAstroSPA(ctx)
|
||||
isNext := p.isNextSPA(ctx)
|
||||
isCRA := p.isCRA(ctx)
|
||||
isAngular := p.isAngular(ctx)
|
||||
isReactRouter := p.isReactRouter(ctx)
|
||||
isExpoSPA := p.isExpoSPA(ctx)
|
||||
|
||||
return (isVite || isAstro || isNext || isCRA || isAngular || isReactRouter || isExpoSPA) && p.getOutputDirectory(ctx) != ""
|
||||
return (isVite || isAstro || isNext || isCRA || isAngular || isExpoSPA) && p.getOutputDirectory(ctx) != ""
|
||||
}
|
||||
|
||||
// returns the canonical lowercase SPA framework name, or "" when none is detected.
|
||||
func (p *NodeProvider) getSPAName(ctx *generate.GenerateContext) string {
|
||||
// TODO react router is not always SPA!
|
||||
if p.isReactRouter(ctx) {
|
||||
if p.isReactRouterSPA(ctx) {
|
||||
return "react-router"
|
||||
} else if p.isVite(ctx) {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if p.isVite(ctx) {
|
||||
return "vite"
|
||||
} else if p.isAstro(ctx) {
|
||||
}
|
||||
if p.isAstro(ctx) {
|
||||
return "astro"
|
||||
} else if p.isNextSPA(ctx) {
|
||||
}
|
||||
if p.isNextSPA(ctx) {
|
||||
return "next"
|
||||
} else if p.isCRA(ctx) {
|
||||
}
|
||||
if p.isCRA(ctx) {
|
||||
return "cra"
|
||||
} else if p.isAngular(ctx) {
|
||||
}
|
||||
if p.isAngular(ctx) {
|
||||
return "angular"
|
||||
} else if p.isExpoSPA(ctx) {
|
||||
}
|
||||
if p.isExpoSPA(ctx) {
|
||||
return "expo"
|
||||
} else {
|
||||
// this could happen if the user forces SPA with env
|
||||
log.Infof("No SPA framework detected")
|
||||
}
|
||||
|
||||
// This can happen when the output directory environment variable forces SPA mode.
|
||||
log.Infof("No SPA framework detected")
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -136,7 +146,9 @@ func (p *NodeProvider) getOutputDirectory(ctx *generate.GenerateContext) string
|
||||
if dir, _ := ctx.Env.GetConfigVariable(OUTPUT_DIR_VAR); dir != "" {
|
||||
outputDir = dir
|
||||
} else if p.isReactRouter(ctx) {
|
||||
if p.isReactRouterSPA(ctx) {
|
||||
outputDir = p.getReactRouterOutputDirectory(ctx)
|
||||
}
|
||||
} else if p.isVite(ctx) {
|
||||
outputDir = p.getViteOutputDirectory(ctx)
|
||||
} else if p.isAstroSPA(ctx) {
|
||||
@@ -164,6 +176,7 @@ func (p *NodeProvider) hasCustomStartCommand(ctx *generate.GenerateContext) bool
|
||||
isCRAStartCommand := startCommand == DefaultCRAStartCommand
|
||||
isExpoStartCommand := startCommand == DefaultExpoStartCommand
|
||||
isNextStartCommand := startCommand == DefaultNextStartCommand
|
||||
isReactRouterStartCommand := startCommand == DefaultReactRouterStartCommand
|
||||
|
||||
return startCommand != "" && !isAngularDefaultStartCommand && !isCRAStartCommand && !isExpoStartCommand && !isNextStartCommand
|
||||
return startCommand != "" && !isAngularDefaultStartCommand && !isCRAStartCommand && !isExpoStartCommand && !isNextStartCommand && !isReactRouterStartCommand
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ func TestVite(t *testing.T) {
|
||||
isCRA bool
|
||||
isAngular bool
|
||||
isReactRouter bool
|
||||
isRRSPA bool
|
||||
isExpo bool
|
||||
outputDir string
|
||||
}{
|
||||
@@ -80,8 +81,17 @@ func TestVite(t *testing.T) {
|
||||
isSPA: true,
|
||||
isVite: true,
|
||||
isReactRouter: true,
|
||||
isRRSPA: true,
|
||||
outputDir: "build/client/",
|
||||
},
|
||||
{
|
||||
name: "react-router-ssr",
|
||||
path: "../../../examples/node-vite-react-router-ssr",
|
||||
isSPA: false,
|
||||
isVite: true,
|
||||
isReactRouter: true,
|
||||
isRRSPA: false,
|
||||
},
|
||||
{
|
||||
name: "expo-spa",
|
||||
path: "../../../examples/expo-spa",
|
||||
@@ -116,6 +126,9 @@ func TestVite(t *testing.T) {
|
||||
isReactRouter := provider.isReactRouter(ctx)
|
||||
require.Equal(t, tt.isReactRouter, isReactRouter)
|
||||
|
||||
isReactRouterSPA := provider.isReactRouterSPA(ctx)
|
||||
require.Equal(t, tt.isRRSPA, isReactRouterSPA)
|
||||
|
||||
isExpo := provider.isExpoSPA(ctx)
|
||||
require.Equal(t, tt.isExpo, isExpo)
|
||||
|
||||
@@ -162,6 +175,16 @@ func TestHasCustomStartCommand(t *testing.T) {
|
||||
path: "../../../examples/expo-spa",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "react router spa default",
|
||||
path: "../../../examples/node-vite-react-router-spa",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "react router ssr default",
|
||||
path: "../../../examples/node-vite-react-router-ssr",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -215,8 +215,11 @@ static exports). Next.js reads `distDir` from your config when set. Set the
|
||||
directory. Railpack uses your app's `build` script to produce the static
|
||||
output.
|
||||
|
||||
Note that is an SPA framework is *not* detected automatically, can you force SPA mode
|
||||
by specifying a `RAILPACK_SPA_OUTPUT_DIR` environment variable. This will enable SPA mode and serve the specified directory as a static site.
|
||||
Note that if a SPA framework is *not* detected automatically, can you force SPA mode
|
||||
by specifying a `RAILPACK_SPA_OUTPUT_DIR` environment variable. This will enable SPA
|
||||
mode and serve the specified directory as a static site. Some of the SPA detection
|
||||
uses regexes on framework configuration files, which will fail if the default framework
|
||||
configuration files are customized.
|
||||
|
||||
Static sites are served using the [Caddy](https://caddyserver.com/) web server
|
||||
and a [default
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.DS_Store
|
||||
.env
|
||||
/node_modules/
|
||||
|
||||
# React Router
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
FROM node:24-alpine AS development-dependencies-env
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN npm ci
|
||||
|
||||
FROM node:24-alpine AS production-dependencies-env
|
||||
COPY ./package.json package-lock.json /app/
|
||||
WORKDIR /app
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
FROM node:24-alpine AS build-env
|
||||
COPY . /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN npm run build
|
||||
|
||||
FROM node:24-alpine
|
||||
COPY ./package.json package-lock.json /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
WORKDIR /app
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -1,12 +1,18 @@
|
||||
#!/usr/bin/env zsh
|
||||
# Description: autogen this repo from the latest version of the RR template
|
||||
|
||||
cd "${0:A:h}"
|
||||
# Description: autogen this repo from the latest version of the RR template
|
||||
|
||||
pnpx create-react-router@latest . --yes --overwrite --no-git-init
|
||||
|
||||
# remove the start script otherwise it won't be treated as an SPA
|
||||
yq eval -i 'del(.scripts.start)' package.json
|
||||
|
||||
# disable SSR for SPA buildg st
|
||||
# disable SSR for SPA build
|
||||
sed -i '' 's/ssr: true/ssr: false/' react-router.config.ts
|
||||
|
||||
# Pin the build-time preview server to IPv4 until React Router connects to its bound address.
|
||||
# https://github.com/remix-run/react-router/issues/15255
|
||||
sed -i '' '/plugins: \[tailwindcss(), reactRouter()\],/a\
|
||||
// Keep React Router'\''s preview server and prerender request on the same address family.\
|
||||
preview: {\
|
||||
host: "127.0.0.1",\
|
||||
},
|
||||
' vite.config.ts
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
{
|
||||
"name": "note-vite-react-router-spa",
|
||||
"name": "node-vite-react-router-spa",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "react-router build",
|
||||
"dev": "react-router dev",
|
||||
"start": "react-router-serve ./build/server/index.js",
|
||||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-router/node": "^7.7.1",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"isbot": "^5.1.27",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router": "^7.7.1"
|
||||
"@react-router/node": "8.0.0",
|
||||
"@react-router/serve": "8.0.0",
|
||||
"isbot": "^5.1.36",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router": "8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "^7.7.1",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.3",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
"@react-router/dev": "8.0.0",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.3"
|
||||
}
|
||||
}
|
||||
+765
-1419
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,6 @@
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"rootDirs": [".", "./.react-router/types"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./app/*"]
|
||||
},
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { reactRouter } from "@react-router/dev/vite";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
|
||||
plugins: [tailwindcss(), reactRouter()],
|
||||
// Keep React Router's preview server and prerender request on the same address family.
|
||||
preview: {
|
||||
host: "127.0.0.1",
|
||||
},
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.DS_Store
|
||||
.env
|
||||
/node_modules/
|
||||
|
||||
# React Router
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
FROM node:20-alpine AS development-dependencies-env
|
||||
FROM node:24-alpine AS development-dependencies-env
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN npm ci
|
||||
|
||||
FROM node:20-alpine AS production-dependencies-env
|
||||
FROM node:24-alpine AS production-dependencies-env
|
||||
COPY ./package.json package-lock.json /app/
|
||||
WORKDIR /app
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
FROM node:20-alpine AS build-env
|
||||
FROM node:24-alpine AS build-env
|
||||
COPY . /app/
|
||||
COPY --from=development-dependencies-env /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
FROM node:24-alpine
|
||||
COPY ./package.json package-lock.json /app/
|
||||
COPY --from=production-dependencies-env /app/node_modules /app/node_modules
|
||||
COPY --from=build-env /app/build /app/build
|
||||
|
||||
@@ -9,22 +9,21 @@
|
||||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-router/node": "^7.7.1",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"isbot": "^5.1.27",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router": "^7.7.1"
|
||||
"@react-router/node": "8.0.0",
|
||||
"@react-router/serve": "8.0.0",
|
||||
"isbot": "^5.1.36",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-router": "8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "^7.7.1",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.3",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
"@react-router/dev": "8.0.0",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.3"
|
||||
}
|
||||
}
|
||||
+800
-1477
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,6 @@
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"rootDirs": [".", "./.react-router/types"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./app/*"]
|
||||
},
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { reactRouter } from "@react-router/dev/vite";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
|
||||
plugins: [tailwindcss(), reactRouter()],
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -46,3 +46,49 @@ backend = "go:github.com/go-delve/delve/cmd/dlv"
|
||||
[[tools."go:github.com/wagoodman/dive"]]
|
||||
version = "0.13.1"
|
||||
backend = "go:github.com/wagoodman/dive"
|
||||
|
||||
[[tools.jq]]
|
||||
version = "1.8.2"
|
||||
backend = "aqua:jqlang/jq"
|
||||
|
||||
[tools.jq."platforms.linux-arm64"]
|
||||
checksum = "sha256:8b85c817833814ddca00a144c33705546355afccf0cf39b188f3cdb48b852309"
|
||||
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-arm64"
|
||||
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012756"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.jq."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:8b85c817833814ddca00a144c33705546355afccf0cf39b188f3cdb48b852309"
|
||||
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-arm64"
|
||||
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012756"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.jq."platforms.linux-x64"]
|
||||
checksum = "sha256:b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f"
|
||||
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-amd64"
|
||||
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012752"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.jq."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:b1c22172dd303f3be49e935aa56aa48a8b7a46e0bc838b4997d3bb451495870f"
|
||||
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-linux-amd64"
|
||||
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012752"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.jq."platforms.macos-arm64"]
|
||||
checksum = "sha256:2d75340ba57a4b4b4c8708a21c2dc8e958a48aaa8bba13b27f77f6e4c0eca07e"
|
||||
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-macos-arm64"
|
||||
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012783"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.jq."platforms.macos-x64"]
|
||||
checksum = "sha256:e94b266e3c26690550006abe63152b782280f4e14374accdf04cbde844f00bc0"
|
||||
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-macos-amd64"
|
||||
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012782"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.jq."platforms.windows-x64"]
|
||||
checksum = "sha256:a6fc67fedaf9128a3309a1e2ebb8b986aeccf70122ee46d2cb4849e423f0c627"
|
||||
url = "https://github.com/jqlang/jq/releases/download/jq-1.8.2/jq-windows-amd64.exe"
|
||||
url_api = "https://api.github.com/repos/jqlang/jq/releases/assets/453012788"
|
||||
provenance = "github-attestations"
|
||||
|
||||
Reference in New Issue
Block a user