-
Notifications
You must be signed in to change notification settings - Fork 14
feat: add GitLab CI/CD integration for setup-vp #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
naokihaba
wants to merge
10
commits into
voidzero-dev:main
Choose a base branch
from
naokihaba:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,685
−3
Draft
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
54d06b9
docs: add GitLab integration RFC
naokihaba 024fde0
feat: add GitLab runtime template and bootstrap flow
naokihaba f222ba2
chore: include gitlab runtime tests in test config
naokihaba 894abf1
docs: document GitLab integration usage and rollout details
naokihaba 155cbb7
feat: add GitLab template bootstrap and documentation page
naokihaba 277acaf
chore: remove generated GitLab runtime explanation page
naokihaba a2cc7ce
fix: align GitLab runtime bootstrap behavior
naokihaba ea2c661
refactor: compile GitLab runtime from TypeScript
naokihaba 45e42bf
docs: document GitLab runtime architecture
naokihaba 23b4700
fix: parse GitLab block YAML install args
naokihaba File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| import{get as e}from"node:http";import{pathToFileURL as t}from"node:url";import n from"node:path";import{createWriteStream as r,existsSync as i,statSync as a,writeFileSync as o}from"node:fs";import{tmpdir as s}from"node:os";import{get as c}from"node:https";import{spawnSync as l}from"node:child_process";import{chmod as u,mkdtemp as d}from"node:fs/promises";function shellQuote(e){return`'${String(e).replaceAll(`'`,`'\\''`)}'`}function exportShellEnv(e,t,n=process.env){!n.SETUP_VP_ENV_FILE||t===void 0||o(n.SETUP_VP_ENV_FILE,`export ${e}=${shellQuote(t)}\n`,{encoding:`utf8`,flag:`a`})}function run(e,t,n={}){let r=l(e,t,{stdio:`inherit`,...n});if(r.error)throw r.error;r.status!==0&&process.exit(r.status??1)}function commandPath(e){let t=l(`sh`,[`-c`,`command -v "${e}"`],{encoding:`utf8`});if(t.status===0)return t.stdout.trim()}function configureAuth(e,t,r=process.env){if(!e)return;let i;try{i=new URL(e)}catch{throw Error(`Invalid registry-url: "${e}". Must be a valid URL.`)}let a=i.href.endsWith(`/`)?i.href:`${i.href}/`,c=``;t&&(c=`${(t.startsWith(`@`)?t:`@${t}`).toLowerCase()}:`);let l=a.replace(/^\w+:/,``).toLowerCase(),u=n.join(s(),`setup-vp-npmrc.${process.pid}`);return o(u,`${l}:_authToken=\${NODE_AUTH_TOKEN}\n${c}registry=${a}\n`,`utf8`),r.NPM_CONFIG_USERCONFIG=u,r.PNPM_CONFIG_USERCONFIG=u,r.NODE_AUTH_TOKEN=r.NODE_AUTH_TOKEN||`XXXXX-XXXXX-XXXXX-XXXXX`,r===process.env&&(exportShellEnv(`NPM_CONFIG_USERCONFIG`,r.NPM_CONFIG_USERCONFIG,r),exportShellEnv(`PNPM_CONFIG_USERCONFIG`,r.PNPM_CONFIG_USERCONFIG,r),exportShellEnv(`NODE_AUTH_TOKEN`,r.NODE_AUTH_TOKEN,r)),u}const f=`v1.12.0`,p=`https://github.com/SocketDev/sfw-free/releases/download/${f}`;function isMuslLinux(){if(process.platform!==`linux`)return!1;try{let e=process.report?.getReport();if(e?.header&&!e.header.glibcVersionRuntime)return!0}catch{}return i(`/etc/alpine-release`)}function getSfwAssetName(e,t,n){if(e===`darwin`){if(t===`x64`)return`sfw-free-macos-x86_64`;if(t===`arm64`)return`sfw-free-macos-arm64`}if(e===`linux`){if(t===`x64`)return n?`sfw-free-musl-linux-x86_64`:`sfw-free-linux-x86_64`;if(t===`arm64`)return n?`sfw-free-musl-linux-arm64`:`sfw-free-linux-arm64`}throw Error(`Unsupported platform/arch for sfw: ${e}/${t}${e===`linux`?` (${n?`musl`:`glibc`})`:``}`)}function sfwAssetName(){try{return getSfwAssetName(process.platform,process.arch,isMuslLinux())}catch{return}}function sfwEnvironmentDescription(){return`process.platform=${process.platform}, process.arch=${process.arch}, musl=${isMuslLinux()}`}function downloadFile(t,n,i=0){if(i>5)return Promise.reject(Error(`too many redirects while downloading ${t}`));let a=t.startsWith(`https:`)?c:e;return new Promise((e,o)=>{a(t,a=>{let s=a.statusCode??0,c=a.headers.location;if(s>=300&&s<400&&c){a.resume(),downloadFile(new URL(c,t).toString(),n,i+1).then(()=>e(),o);return}if(s!==200){a.resume(),o(Error(`download failed with HTTP ${s}: ${t}`));return}let l=r(n);a.pipe(l),l.on(`finish`,()=>l.close(()=>e())),l.on(`error`,o)}).on(`error`,o)})}async function setupSfw(e,t=process.env){if(t.SETUP_VP_SFW!==`true`)return`vp`;if(e.length===0)return console.log(`setup-vp: sfw was requested but run-install is disabled; sfw will not be invoked.`),`vp`;let r=commandPath(`sfw`);if(r)return console.log(`setup-vp: using existing sfw on PATH: ${r}`),`sfw`;let i=sfwAssetName();if(!i)return console.error(`setup-vp: sfw has no published binary for this runner's platform/architecture (${sfwEnvironmentDescription()}) and none was found on PATH; falling back to plain vp install.`),`vp`;let a=await d(n.join(s(),`setup-vp-sfw-`)),o=n.join(a,`sfw`),c=`${p}/${i}`;for(let e=1;e<=2;e+=1)try{return console.log(`setup-vp: installing sfw ${f} from ${c}`),await downloadFile(c,o),await u(o,493),t.PATH=`${a}:${t.PATH||``}`,exportShellEnv(`PATH`,t.PATH,t),`sfw`}catch(t){if(e===2)throw t;await new Promise(e=>setTimeout(e,2e3))}throw Error(`failed to install sfw after retrying`)}function parseScalar(e){let t=String(e||``).trim();return t.startsWith(`"`)&&t.endsWith(`"`)||t.startsWith(`'`)&&t.endsWith(`'`)?t.slice(1,-1):t}function parseFlowArray(e){let t=String(e||``).trim();if(!t.startsWith(`[`)||!t.endsWith(`]`))throw Error(`args must be an array, got: ${e}`);let n=t.slice(1,-1).trim();if(!n)return[];let r=[],i=``,a=``;for(let e of n){if(a){e===a&&(a=``),i+=e;continue}if(e===`'`||e===`"`){a=e,i+=e;continue}if(e===`,`){r.push(parseScalar(i)),i=``;continue}i+=e}return i.trim()&&r.push(parseScalar(i)),r}function parseKeyValue(e){let t=e.indexOf(`:`);if(!(t<0))return[e.slice(0,t).trim(),e.slice(t+1).trim()]}function countIndent(e){return e.length-e.trimStart().length}function parseBlockArray(e,t,n){let r=[],i=t;for(;i<e.length;){let t=e[i],a=countIndent(t),o=t.trimStart();if(a<=n)break;if(!o.startsWith(`-`))throw Error(`invalid args line: ${t}`);let s=o.slice(1).trim();if(!s)throw Error(`args entries must be strings: ${t}`);r.push(parseScalar(s)),i+=1}if(r.length===0)throw Error(`args must be an array`);return{values:r,nextIndex:i}}function assignValue(e,t,n){if(t===`cwd`)return e.cwd=parseScalar(n),!1;if(t===`args`)return n?(e.args=parseFlowArray(n),!1):!0;throw Error(`unsupported run-install key: ${t}`)}function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function validateRunInstallEntry(e){if(!isRecord(e))throw Error(`run-install entries must be objects`);for(let t of Object.keys(e))if(t!==`cwd`&&t!==`args`)throw Error(`unsupported run-install key: ${t}`);let t={};if(e.cwd!==void 0){if(typeof e.cwd!=`string`)throw Error(`run-install.cwd must be a string`);t.cwd=e.cwd}if(e.args!==void 0){if(!Array.isArray(e.args)||e.args.some(e=>typeof e!=`string`))throw Error(`run-install.args must be an array of strings`);t.args=e.args}return t}function validateRunInstallInput(e){return e===null||typeof e==`boolean`?e:Array.isArray(e)?e.map(validateRunInstallEntry):validateRunInstallEntry(e)}function parseObject(e){let t={};for(let n=0;n<e.length;n+=1){let r=e[n],i=r.trim();if(!i||i.startsWith(`#`))continue;let a=parseKeyValue(i);if(!a)throw Error(`invalid run-install line: ${r}`);if(assignValue(t,a[0],a[1])){let i=parseBlockArray(e,n+1,countIndent(r));t.args=i.values,n=i.nextIndex-1}}return t}function parseYamlSubset(e){let t=e.split(/\r?\n/).filter(e=>e.trim()&&!e.trim().startsWith(`#`));if(t.length===0)return[];if(!t[0].trimStart().startsWith(`-`))return[parseObject(t)];let n=countIndent(t[0]),r=[],i;for(let e=0;e<t.length;e+=1){let a=t[e],o=countIndent(a),s=a.trimStart();if(o===n&&s.startsWith(`-`)){i&&r.push(i),i={};let n=s.slice(1).trim();if(n){let r=parseKeyValue(n);if(!r)throw Error(`invalid run-install line: ${a}`);if(assignValue(i,r[0],r[1])){let n=parseBlockArray(t,e+1,o);i.args=n.values,e=n.nextIndex-1}}continue}if(!i)throw Error(`invalid run-install line: ${a}`);let c=parseKeyValue(s);if(!c)throw Error(`invalid run-install line: ${a}`);if(assignValue(i,c[0],c[1])){let n=parseBlockArray(t,e+1,o);i.args=n.values,e=n.nextIndex-1}}return i&&r.push(i),r}function parseRunInstall(e){let t=String(e||``).trim();return t?normalizeRunInstallInput(parseRunInstallInput(t)):[]}function parseRunInstallInput(e){try{return validateRunInstallInput(JSON.parse(e))}catch(e){if(!(e instanceof SyntaxError))throw formatRunInstallError(e)}try{return validateRunInstallInput(parseYamlSubset(e))}catch(e){throw formatRunInstallError(e)}}function normalizeRunInstallInput(e){return e?e===!0?[{}]:Array.isArray(e)?e:[e]:[]}function formatRunInstallError(e){return e instanceof Error?e:Error(String(e))}function runInstall(e,t,r){for(let i of e){let e=i.cwd?n.resolve(t,i.cwd):t,a=[`install`,...i.args||[]],o=r===`sfw`?[`vp`,...a]:a;console.log(`setup-vp: running ${r} ${o.join(` `)} in ${e}`),run(r,o,{cwd:e})}}function resolveProjectDir(e=process.env){let t=e.SETUP_VP_WORKING_DIRECTORY||`.`,r=n.isAbsolute(t)?t:n.join(e.CI_PROJECT_DIR||process.cwd(),t);try{if(!a(r).isDirectory())throw Error(`working-directory is not a directory: ${t} (resolved to ${r})`)}catch(e){throw e instanceof Error&&`code`in e&&e.code===`ENOENT`?Error(`working-directory not found: ${t} (resolved to ${r})`):e}return r}function fail(e){console.error(`setup-vp: ${e}`),process.exit(1)}async function main(){let e=resolveProjectDir(process.env);configureAuth(process.env.SETUP_VP_REGISTRY_URL||``,process.env.SETUP_VP_SCOPE||``);let t=parseRunInstall(process.env.SETUP_VP_RUN_INSTALL||`true`);runInstall(t,e,await setupSfw(t)),run(`vp`,[`--version`])}if(process.argv[1]&&import.meta.url===t(process.argv[1]).href)try{await main()}catch(e){fail(e instanceof Error?e.message:String(e))}export{main}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| #!/usr/bin/env bash | ||
| set -eu | ||
|
|
||
| # GitLab remote includes can only start from YAML, so setup-vp.yml downloads | ||
| # this bootstrap first. Keep this file as a thin shell entrypoint: install vp, | ||
| # export PATH for the rest of the job, verify Node.js is available in the | ||
| # runner image, then download and execute the compiled TypeScript runtime from | ||
| # dist/gitlab/index.mjs. | ||
|
|
||
| setup_vp_download() { | ||
| setup_vp_url="$1" | ||
| setup_vp_out="$2" | ||
|
|
||
| if command -v curl >/dev/null 2>&1; then | ||
| curl -fsSL --connect-timeout 5 --max-time 60 "$setup_vp_url" -o "$setup_vp_out" | ||
| elif command -v wget >/dev/null 2>&1; then | ||
| wget -qO "$setup_vp_out" "$setup_vp_url" | ||
| else | ||
| echo "setup-vp: curl or wget is required to download files." >&2 | ||
| return 127 | ||
| fi | ||
| } | ||
|
|
||
| setup_vp_shell_quote() { | ||
| printf "'%s'" "$(printf "%s" "$1" | sed "s/'/'\\\\''/g")" | ||
| } | ||
|
|
||
| setup_vp_export_env() { | ||
| if [ -z "${SETUP_VP_ENV_FILE:-}" ]; then | ||
| return 0 | ||
| fi | ||
|
|
||
| setup_vp_name="$1" | ||
| setup_vp_value="$2" | ||
| printf "export %s=" "$setup_vp_name" >> "$SETUP_VP_ENV_FILE" | ||
| setup_vp_shell_quote "$setup_vp_value" >> "$SETUP_VP_ENV_FILE" | ||
| printf "\n" >> "$SETUP_VP_ENV_FILE" | ||
| } | ||
|
|
||
| setup_vp_install_viteplus_from() { | ||
| setup_vp_url="$1" | ||
| rm -f "$setup_vp_install_tmp" | ||
|
|
||
| setup_vp_download "$setup_vp_url" "$setup_vp_install_tmp" | ||
| VP_VERSION="$SETUP_VP_VERSION" VITE_PLUS_VERSION="$SETUP_VP_VERSION" bash "$setup_vp_install_tmp" | ||
| } | ||
|
|
||
| setup_vp_install_viteplus() { | ||
| setup_vp_round=1 | ||
| while [ "$setup_vp_round" -le 2 ]; do | ||
| for setup_vp_url in \ | ||
| "https://viteplus.dev/install.sh" \ | ||
| "https://raw.githubusercontent.com/voidzero-dev/vite-plus/main/packages/cli/install.sh" | ||
| do | ||
| echo "setup-vp: installing Vite+ ${SETUP_VP_VERSION} from ${setup_vp_url}" | ||
| if setup_vp_install_viteplus_from "$setup_vp_url"; then | ||
| return 0 | ||
| fi | ||
| echo "setup-vp: install attempt failed; retrying if another source is available." >&2 | ||
| done | ||
| setup_vp_round=$((setup_vp_round + 1)) | ||
| if [ "$setup_vp_round" -le 2 ]; then | ||
| sleep 2 | ||
| fi | ||
| done | ||
|
|
||
| echo "setup-vp: failed to install Vite+ after retrying all installer URLs." >&2 | ||
| return 1 | ||
| } | ||
|
|
||
| SETUP_VP_VERSION="${SETUP_VP_VERSION:-latest}" | ||
| SETUP_VP_SETUP_REF="${SETUP_VP_SETUP_REF:-v1}" | ||
| setup_vp_install_tmp="${TMPDIR:-/tmp}/setup-vp-install.$$" | ||
| setup_vp_runtime_tmp="${TMPDIR:-/tmp}/setup-vp-gitlab-runtime.$$.mjs" | ||
| trap 'rm -f "$setup_vp_install_tmp" "$setup_vp_runtime_tmp"' EXIT | ||
|
|
||
| setup_vp_install_viteplus | ||
| export PATH="$HOME/.vite-plus/bin:$PATH" | ||
| setup_vp_export_env PATH "$PATH" | ||
|
|
||
| if ! command -v node >/dev/null 2>&1; then | ||
| echo "setup-vp: Node.js is required in the GitLab runner image to execute the setup-vp runtime." >&2 | ||
| return 127 2>/dev/null || exit 127 | ||
| fi | ||
|
|
||
| setup_vp_runtime_url="https://raw.githubusercontent.com/voidzero-dev/setup-vp/${SETUP_VP_SETUP_REF}/dist/gitlab/index.mjs" | ||
| setup_vp_download "$setup_vp_runtime_url" "$setup_vp_runtime_tmp" | ||
| node "$setup_vp_runtime_tmp" | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.