From e095c3fe3a432c40e8dbc2e435e67a3208d3e5fc Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 4 Sep 2025 16:09:57 -0500 Subject: [PATCH 001/144] npx sv create --- .gitignore | 24 + .npmrc | 1 + .prettierignore | 9 + .prettierrc | 16 + eslint.config.js | 40 + package-lock.json | 4396 ++++++++++++++++++++++++++++++++++++ package.json | 38 + src/app.css | 2 + src/app.d.ts | 13 + src/app.html | 11 + src/lib/assets/favicon.ico | Bin 0 -> 318 bytes src/lib/index.ts | 1 + src/routes/+layout.svelte | 13 + src/routes/+page.svelte | 2 + static/robots.txt | 2 + svelte.config.js | 12 + tsconfig.json | 19 + vite.config.ts | 7 + 18 files changed, 4606 insertions(+) create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 eslint.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/app.css create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/lib/assets/favicon.ico create mode 100644 src/lib/index.ts create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+page.svelte create mode 100644 static/robots.txt create mode 100644 svelte.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index e15aa943..89c72b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +node_modules + # Vagrant folder .vagrant/ @@ -6,9 +8,31 @@ local.env *.aes aws.env +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + # IDE project files .idea/ nbproject/ +# OS +.DS_Store +Thumbs.db + #yii runtime stuff application/runtime/ + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..b6f27f13 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..7d74fe24 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..8103a0b5 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/app.css" +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..a9628784 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +import prettier from 'eslint-config-prettier'; +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import globals from 'globals'; +import { fileURLToPath } from 'node:url'; +import ts from 'typescript-eslint'; +import svelteConfig from './svelte.config.js'; + +const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); + +export default ts.config( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs.recommended, + prettier, + ...svelte.configs.prettier, + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + }, + rules: { + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off' + } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser, + svelteConfig + } + } + } +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..4652dab2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4396 @@ +{ + "name": "appbuilder-buildengine-api", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "appbuilder-buildengine-api", + "version": "0.0.1", + "devDependencies": { + "@eslint/compat": "^1.2.5", + "@eslint/js": "^9.18.0", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.22.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "@tailwindcss/vite": "^4.0.0", + "daisyui": "^5.1.6", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-svelte": "^3.0.0", + "globals": "^16.0.0", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0", + "vite": "^7.0.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", + "integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.3.2.tgz", + "integrity": "sha512-jRNwzTbd6p2Rw4sZ1CgWRS8YMtqG15YyZf7zvb6gY2rB2u6n+2Z+ELW0GtL0fQgyl0pr4Y/BzBfng/BdsereRA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.40 || 9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", + "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", + "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz", + "integrity": "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz", + "integrity": "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz", + "integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz", + "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz", + "integrity": "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz", + "integrity": "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz", + "integrity": "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz", + "integrity": "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz", + "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz", + "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz", + "integrity": "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz", + "integrity": "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz", + "integrity": "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz", + "integrity": "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz", + "integrity": "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz", + "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz", + "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz", + "integrity": "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz", + "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz", + "integrity": "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", + "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.3.1.tgz", + "integrity": "sha512-PSoGfa9atkmuixe7jvuS2tsUohVZF20So87ASzfMRGTTNqEd8s48KAodlv3CzHwq9XO/BM8KsQLpqqsr/6dmuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.37.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.37.0.tgz", + "integrity": "sha512-xgKtpjQ6Ry4mdShd01ht5AODUsW7+K1iValPDq7QX8zI1hWOKREH9GjG8SRCN5tC4K7UXmMhuQam7gbLByVcnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.3.2", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.4.tgz", + "integrity": "sha512-4jfkfvsGI+U2OhHX8OPCKtMCf7g7ledXhs3E6UcA4EY0jQWsiVbe83pTAHp9XTifzYNOiD4AJieJUsI0qqxsbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "magic-string": "^0.30.17", + "vitefu": "^1.1.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", + "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.1" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || ^7.0.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", + "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.18", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.13" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz", + "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-x64": "4.1.13", + "@tailwindcss/oxide-freebsd-x64": "4.1.13", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-x64-musl": "4.1.13", + "@tailwindcss/oxide-wasm32-wasi": "4.1.13", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz", + "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz", + "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz", + "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz", + "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz", + "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz", + "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz", + "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz", + "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz", + "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz", + "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@emnapi/wasi-threads": "^1.0.4", + "@napi-rs/wasm-runtime": "^0.2.12", + "@tybys/wasm-util": "^0.10.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", + "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", + "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.13.tgz", + "integrity": "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.13", + "@tailwindcss/oxide": "4.1.13", + "tailwindcss": "4.1.13" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.42.0.tgz", + "integrity": "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/type-utils": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.42.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.42.0.tgz", + "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.42.0.tgz", + "integrity": "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.42.0", + "@typescript-eslint/types": "^8.42.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz", + "integrity": "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz", + "integrity": "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.42.0.tgz", + "integrity": "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.42.0.tgz", + "integrity": "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz", + "integrity": "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.42.0", + "@typescript-eslint/tsconfig-utils": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.42.0.tgz", + "integrity": "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz", + "integrity": "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/daisyui": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.1.6.tgz", + "integrity": "sha512-KCzv25f+3lwWbfnPZZG9Xo0kSGO1NSysyIiS5AoCtDotIrvvArggHklCey1Fg6U2gZuqxsi2rptT1q3khoYCMw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", + "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", + "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.34.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.12.1.tgz", + "integrity": "sha512-gVwqUHUiD3r/T7wLqGggl24afEkUiJqV1BjBXu0C0Y2l6dB9InYOddksccrL8oOJ+DP4lLFVXVjzHexTbPs/1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "esutils": "^2.0.3", + "globals": "^16.0.0", + "known-css-properties": "^0.37.0", + "postcss": "^8.4.49", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^8.57.1 || ^9.0.0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", + "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.18", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", + "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", + "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.0", + "@rollup/rollup-android-arm64": "4.50.0", + "@rollup/rollup-darwin-arm64": "4.50.0", + "@rollup/rollup-darwin-x64": "4.50.0", + "@rollup/rollup-freebsd-arm64": "4.50.0", + "@rollup/rollup-freebsd-x64": "4.50.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", + "@rollup/rollup-linux-arm-musleabihf": "4.50.0", + "@rollup/rollup-linux-arm64-gnu": "4.50.0", + "@rollup/rollup-linux-arm64-musl": "4.50.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", + "@rollup/rollup-linux-ppc64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-musl": "4.50.0", + "@rollup/rollup-linux-s390x-gnu": "4.50.0", + "@rollup/rollup-linux-x64-gnu": "4.50.0", + "@rollup/rollup-linux-x64-musl": "4.50.0", + "@rollup/rollup-openharmony-arm64": "4.50.0", + "@rollup/rollup-win32-arm64-msvc": "4.50.0", + "@rollup/rollup-win32-ia32-msvc": "4.50.0", + "@rollup/rollup-win32-x64-msvc": "4.50.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.38.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.38.6.tgz", + "integrity": "sha512-ltBPlkvqk3bgCK7/N323atUpP3O3Y+DrGV4dcULrsSn4fZaaNnOmdplNznwfdWclAgvSr5rxjtzn/zJhRm6TKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^2.1.0", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.1.tgz", + "integrity": "sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.1.tgz", + "integrity": "sha512-0Iztj5vcOVOVkhy1pbo5uA9r+d3yaVoE5XPc9eABIWDOSJZ2mOsZ4D+t45rphWCOr0uMw3jtSG2fh2e7GvKnPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte/node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", + "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.42.0.tgz", + "integrity": "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.4.tgz", + "integrity": "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..7e38e844 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "appbuilder-buildengine-api", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint ." + }, + "devDependencies": { + "@eslint/compat": "^1.2.5", + "@eslint/js": "^9.18.0", + "@sveltejs/adapter-node": "^5.2.12", + "@sveltejs/kit": "^2.22.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "@tailwindcss/vite": "^4.0.0", + "daisyui": "^5.1.6", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-svelte": "^3.0.0", + "globals": "^16.0.0", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0", + "vite": "^7.0.4" + } +} diff --git a/src/app.css b/src/app.css new file mode 100644 index 00000000..6c77f21a --- /dev/null +++ b/src/app.css @@ -0,0 +1,2 @@ +@import 'tailwindcss'; +@plugin 'daisyui'; \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 00000000..da08e6da --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 00000000..f273cc58 --- /dev/null +++ b/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/lib/assets/favicon.ico b/src/lib/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..580ed732e86556ec57f3f3395a210246d679c076 GIT binary patch literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|4HXaJKC0wf0lAEr2iX{M9K3=BR0 y!E90pK{x=K$Oz&POT#sS8N$ZKhC)h8ip0_|-T#43{vnSYgXBQCu@O54$pHYIza?e> literal 0 HcmV?d00001 diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 00000000..856f2b6c --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 00000000..8b927f52 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,13 @@ + + + + + SIL AppBuilder Administration + + +{@render children?.()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 00000000..cc88df0e --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,2 @@ +

Welcome to SvelteKit

+

Visit svelte.dev/docs/kit to read the documentation

diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 00000000..1f53798b --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 00000000..03c17f28 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,12 @@ +import adapter from '@sveltejs/adapter-node'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + kit: { adapter: adapter() } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..a5567ee6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..2d35c4f5 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import tailwindcss from '@tailwindcss/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()] +}); From de28f9f585844795244051224e15159b90ac9a28 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 4 Sep 2025 16:45:26 -0500 Subject: [PATCH 002/144] Copy styling from Scriptoria --- src/app.css | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/src/app.css b/src/app.css index 6c77f21a..86667a7b 100644 --- a/src/app.css +++ b/src/app.css @@ -1,2 +1,143 @@ @import 'tailwindcss'; -@plugin 'daisyui'; \ No newline at end of file + +@plugin 'daisyui' { + themes: + light --default, + dark --prefersdark; +} +@plugin "daisyui/theme" { + name: 'light'; + default: true; + prefersdark: false; + color-scheme: 'light'; + --color-base-100: oklch(100% 0 0); + --color-base-200: oklch(98.46% 0.0017 247.84); + --color-base-300: oklch(86.48% 0.0099 252.82); + --color-base-content: oklch(27.02% 0.0275 257.53); + --color-primary: oklch(32.01% 0.0736 260.63); + --color-primary-content: oklch(100% 0 0); + --color-secondary: oklch(48.26% 0.0303 268.49); + --color-secondary-content: oklch(100% 0 0); + --color-accent: oklch(64.6% 0.1423 253.92); + --color-accent-content: oklch(100% 0 0); + --color-neutral: oklch(96.42% 0 0); + --color-neutral-content: oklch(27.02% 0.0275 257.53); + --color-info: oklch(74% 0.16 232.661); + --color-info-content: oklch(29% 0.066 243.157); + --color-success: oklch(76% 0.177 163.223); + --color-success-content: oklch(37% 0.077 168.94); + --color-warning: oklch(82% 0.189 84.429); + --color-warning-content: oklch(41% 0.112 45.904); + --color-error: oklch(71% 0.194 13.428); + --color-error-content: oklch(27% 0.105 12.094); + --radius-selector: 0.5rem; + --radius-field: 0.5rem; + --radius-box: 1rem; + --size-selector: 0.25rem; + --size-field: 0.28125rem; + --border: 1px; + --depth: 0; + --noise: 0; +} +@plugin "daisyui/theme" { + name: 'dark'; + default: false; + prefersdark: true; + color-scheme: 'dark'; + --color-base-100: oklch(25.33% 0.016 252.42); + --color-base-200: oklch(23.26% 0.014 253.1); + --color-base-300: oklch(21.15% 0.012 254.09); + --color-base-content: oklch(97.807% 0.029 256.847); + --color-primary: oklch(32.01% 0.0736 260.63); + --color-primary-content: oklch(100% 0 0); + --color-secondary: oklch(48.26% 0.0303 268.49); + --color-secondary-content: oklch(100% 0 0); + --color-accent: oklch(51.71% 0.1678 264.26); + --color-accent-content: oklch(100% 0 0); + --color-neutral: oklch(37.74% 0.0226 261.23); + --color-neutral-content: oklch(100% 0 0); + --color-info: oklch(74% 0.16 232.661); + --color-info-content: oklch(29% 0.066 243.157); + --color-success: oklch(76% 0.177 163.223); + --color-success-content: oklch(37% 0.077 168.94); + --color-warning: oklch(82% 0.189 84.429); + --color-warning-content: oklch(41% 0.112 45.904); + --color-error: oklch(71% 0.194 13.428); + --color-error-content: oklch(27% 0.105 12.094); + --radius-selector: 0.5rem; + --radius-field: 0.5rem; + --radius-box: 1rem; + --size-selector: 0.25rem; + --size-field: 0.28125rem; + --border: 1px; + --depth: 0; + --noise: 0; +} + +/* + The default border color has changed to `currentColor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } +} + +/* These styles are in this layer so other utility styles can apply normally */ +@layer utilities { + h1 { + /* text-2xl */ + font-size: var(--text-2xl) /* 1.5rem = 24px */; + line-height: var(--tw-leading, var(--text-2xl--line-height) /* calc(2 / 1.5) ≈ 1.3333 */); + /* font-bold */ + font-weight: var(--font-weight-bold) /* 700 */; + /* p-8 */ + padding: calc(var(--spacing) * 8) /* 2rem = 32px */; + } + + h2 { + /* text-xl */ + font-size: var(--text-xl) /* 1.25rem = 20px */; + line-height: var(--tw-leading, var(--text-xl--line-height) /* calc(1.75 / 1.25) ≈ 1.4 */); + /* font-bold */ + font-weight: var(--font-weight-bold) /* 700 */; + /* p-4 */ + padding: calc(var(--spacing) * 4) /* 1rem = 16px */; + } + + h3 { + /* text-lg */ + font-size: var(--text-lg) /* 1.25rem = 20px */; + line-height: var(--tw-leading, var(--text-lg--line-height) /* calc(1.75 / 1.125) ≈ 1.5556 */); + /* font-bold */ + font-weight: var(--font-weight-bold) /* 700 */; + /* p-2 */ + padding: calc(var(--spacing) * 2) /* 0.5rem = 8px */; + } + * { + font-size: 15px; + } +} + +.dropdown-content { + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); +} +a.link { + color: #55f; +} + +.toggle { + --size: 2rem; + --radius-selector: calc(infinity * 1px); +} From fcb72ae7319f8c37a43ca90a6e6e731be039833e Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 4 Sep 2025 16:46:07 -0500 Subject: [PATCH 003/144] Check in .vscode/settings.json --- .vscode/settings.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a0473ae5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "svelte.plugin.css.diagnostics.enable": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + }, + "typescript.preferences.preferTypeOnlyAutoImports": false +} \ No newline at end of file From 90c6b71f3e392b308e7bf237d8f233a71e09be6f Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 4 Sep 2025 17:31:04 -0500 Subject: [PATCH 004/144] Basic structure Home and About pages Stubs for everything else --- src/app.css | 6 ++ src/lib/components/Breadcrumbs.svelte | 15 ++++ src/routes/+layout.svelte | 36 ++++++++- src/routes/+page.svelte | 74 +++++++++++++++++- src/routes/about/+page.svelte | 29 +++++++ src/routes/build-admin/+page.svelte | 2 + src/routes/client-admin/+page.svelte | 2 + src/routes/contact/+page.svelte | 2 + src/routes/job-admin/+page.svelte | 2 + src/routes/operation-queue-admin/+page.svelte | 2 + src/routes/project-admin/+page.svelte | 2 + src/routes/release-admin/+page.svelte | 2 + static/SILLogoBlue132x184.png | Bin 0 -> 12602 bytes 13 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 src/lib/components/Breadcrumbs.svelte create mode 100644 src/routes/about/+page.svelte create mode 100644 src/routes/build-admin/+page.svelte create mode 100644 src/routes/client-admin/+page.svelte create mode 100644 src/routes/contact/+page.svelte create mode 100644 src/routes/job-admin/+page.svelte create mode 100644 src/routes/operation-queue-admin/+page.svelte create mode 100644 src/routes/project-admin/+page.svelte create mode 100644 src/routes/release-admin/+page.svelte create mode 100644 static/SILLogoBlue132x184.png diff --git a/src/app.css b/src/app.css index 86667a7b..421c1462 100644 --- a/src/app.css +++ b/src/app.css @@ -141,3 +141,9 @@ a.link { --size: 2rem; --radius-selector: calc(infinity * 1px); } + +body { + height: 100vh; + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/src/lib/components/Breadcrumbs.svelte b/src/lib/components/Breadcrumbs.svelte new file mode 100644 index 00000000..e0698c76 --- /dev/null +++ b/src/lib/components/Breadcrumbs.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 8b927f52..bedb961f 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,8 +1,13 @@ @@ -10,4 +15,33 @@ SIL AppBuilder Administration -{@render children?.()} +
+ +
+
+ {@render children?.()} +
+
+
© SIL International {new Date().getFullYear()}
+
+ Powered by SvelteKit +
+
+ + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc88df0e..29775e1c 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,72 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+
+
+

App Publishing Service

+

Administration

+
+
+
+
+
+

Job

+

+ View, edit, or remove entries from the job table. Jobs point to the AWS S3 repository + that contains the source for the builds and publishes associated with this job. Deleting + job entries also deletes any associated builds and releases associated with the job. +

+

Job Administration »

+
+
+

Build

+

+ View, edit, or remove entries from the build table. Each entry in this table contains a + url link to the instance of the associated AWS Codebuild build attempt. Deleting the + build also deletes any releases associated with this build. +

+

Build Administration »

+
+
+

Release

+

+ View, edit, or remove entries from the release table. Entries in this table relate to + attempts to publish builds in Google Play store or other customized locations. Each + entry in this table contains a url link to the instance of the associated AWS Codebuild + publish attempt. +

+

Release Administration »

+
+
+
+
+

Client

+

+ View, edit, or remove entries from the client table. Used if multiple Scriptoria sites + are sending requests to the build engine. Access tokens, which are used for the + Authentication: Bearer fields of requests are entered along with a prefix that is used + in naming jobs associated with this client. +

+

Client Administration »

+
+
+

Operation Queue

+

+ View, edit, or remove entries from the operation queue table. Entries in this table + relate to internal operations that are either queued to be performed, waiting to be + retried, or have failed the maximum number of times and are present for reporting + purposes only. +

+

+ Operation Queue Administration » +

+
+
+

Project

+

+ View, edit, or remove entries from the project table. Each entry contains a link to be + used by Scripture App Builder to create or update a source repository in AWS S3. +

+

Project Administration »

+
+
+
+
+
diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte new file mode 100644 index 00000000..12f017a0 --- /dev/null +++ b/src/routes/about/+page.svelte @@ -0,0 +1,29 @@ + + +
+ +
  • Home
  • +
  • App Publishing Service
  • +
    +
    + SIL International Logo +

    App Publishing Service

    +

    + Copyright 2011-2016 SIL International +

    +

    Credits

    +

    + Chris Hubbard (SIL International) : Dev + Lead, Programming +

    +

    + David Moore (SIL International) + : Programming +

    +

    + Rick MacLean (SIL International) : Dev Ops +

    +
    +
    diff --git a/src/routes/build-admin/+page.svelte b/src/routes/build-admin/+page.svelte new file mode 100644 index 00000000..154b3734 --- /dev/null +++ b/src/routes/build-admin/+page.svelte @@ -0,0 +1,2 @@ + +Under construction \ No newline at end of file diff --git a/src/routes/client-admin/+page.svelte b/src/routes/client-admin/+page.svelte new file mode 100644 index 00000000..154b3734 --- /dev/null +++ b/src/routes/client-admin/+page.svelte @@ -0,0 +1,2 @@ + +Under construction \ No newline at end of file diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte new file mode 100644 index 00000000..154b3734 --- /dev/null +++ b/src/routes/contact/+page.svelte @@ -0,0 +1,2 @@ + +Under construction \ No newline at end of file diff --git a/src/routes/job-admin/+page.svelte b/src/routes/job-admin/+page.svelte new file mode 100644 index 00000000..154b3734 --- /dev/null +++ b/src/routes/job-admin/+page.svelte @@ -0,0 +1,2 @@ + +Under construction \ No newline at end of file diff --git a/src/routes/operation-queue-admin/+page.svelte b/src/routes/operation-queue-admin/+page.svelte new file mode 100644 index 00000000..154b3734 --- /dev/null +++ b/src/routes/operation-queue-admin/+page.svelte @@ -0,0 +1,2 @@ + +Under construction \ No newline at end of file diff --git a/src/routes/project-admin/+page.svelte b/src/routes/project-admin/+page.svelte new file mode 100644 index 00000000..154b3734 --- /dev/null +++ b/src/routes/project-admin/+page.svelte @@ -0,0 +1,2 @@ + +Under construction \ No newline at end of file diff --git a/src/routes/release-admin/+page.svelte b/src/routes/release-admin/+page.svelte new file mode 100644 index 00000000..154b3734 --- /dev/null +++ b/src/routes/release-admin/+page.svelte @@ -0,0 +1,2 @@ + +Under construction \ No newline at end of file diff --git a/static/SILLogoBlue132x184.png b/static/SILLogoBlue132x184.png new file mode 100644 index 0000000000000000000000000000000000000000..14895e5a38a5b862087ccb682911a9db2e107d10 GIT binary patch literal 12602 zcmcJ0Wl&pR^zFr=cq#5qf#U8IFAl{i?(XjHQi@aDq4=*zaZ;qXyM&;{r8x9|`)1zz z_GaFfmzgA!G zUg0Aucnd(ahyh=txyb9e0|55UzdKyvcNGlqO=1rjT@OuXD-Ul|H%q|V+ndeS$===E z)Wwp`+08olRD=Wor~n0N$&WsHCttmN-ps!YZgMy0ZphOXMNJvRyph61#Qg~$7E^h< zt#vWz5T}5sex_x9Op~I_m&jyvjD!v!hbfstVR!HjQQoxr>$_uu~A(bU_`vP+F6us~v9sBR+it5bNw(QmS)L!%`m(;?I!RL4Z7 zgc0Pl$6*cT5ub3p5*pC^lW@HxmpJ>`P=#gxzaMlL%#O-0M~r;e(KotWRMR~3F2&Om zxpo{d=2s%w3YSo|zP|KWcs|-qkicH=aZ36dh=@r77paBG)V2`PZ};4zmB>_CJ=8av zE}*A7AL(sI`of6BSy#Y+N;*q=n|K(WvSD=clHZ&s*HBLy7PziNA#SW6ewMrkdo0mu zJbF(oO`~YpzS@g(v26TOw<8jLDD~vJnI+c7UdsP)?b7(bt7xCwAjn9$NjW}LU-3%4 zo}e~JnY3Y#{T(VH3dIF?wCQrK>8BOsN*(m!HCHU>Cl7dh&);mL(MWgpufq0;^0UYi zApUZ+Q?u*Q zZk2Sk2mj|aXe@`578clO7R2O4yt7z!w#Z}GaHb&`S3`Qynj5|%@)w-PbR{k)=Mu_u z*C%1m@w_K{pVJ?-OK!ytnww|v3m%U~$mSB;T_qPY>m@?hT{Y={mV)XKf3SZsz5S`N zv!f$P*Ut}aZ8>@je_qGEd>}xjq3#_(!$Y|ikPz3ES$o^AA6 zM3N3l#r#kEcuLd*X}u%D#q$dkHpE~WWK-Z3Y`wZ{R{9h~K$^VU%3N20bvck!*4irk zEoKtSWPIZ4n;7NFEW-3G3XBpDrFUInqprm(pZ#YTudurQ>fGs(H{5#buY=mZVWl7cT=k`iug()t1*La}oFnB91Fmq~3mT*#U!dwc% z<0Fu(ay7O4VdV5E;zjJv#+j{}{zI(QMEcy;^Ex4Pk7+1^%9$fpHYz+}p!njsa3SvQ z>V4HR&ySGsbvw7CLt?4(mrduW@a&jT5=rT8>>i z0mOnFUJG!K-_q)^xT>yD-f=y;{dl|$&7Zit9C`bh#{YPrOF&&W@^g+XYg<+Qh`a3V zWk2>1x|-VjRGqz1RqfE@4>pNG_be{84mmE*6iRSXR27A*N;#g%ugw~Igu5U1-<;>Q z5Pi>Q8+->2WV(jjKBY^HAF(c}+<&nrpSeGI2ps?ltoKd-mbsT=duw-R5X+2(o|%BjbHL$rMxY5l>3zSUCK84<}h@K z0h7a5iLQUf8H@CV0e*Xt^0Sitib?Po>Fmp?GxyPrAwBs!TBRT2VYt2>T={AG z^~36Ck8G;F=$k(7R_rv9mvJbI&t+QR*Br9Xt{7(bSCROeS!8c!N5yISu#_t@?X3Ggw)92lNb+q-GhSqz z0g9rwln(Pz<3sDLRnOnS47|@$V`6nlJ{PB{Uq^{NFqd0{gx>m!xK14K4Vly`WTI|Y zRfTg5Pp=$8L>6w?-?9djp2f5z9i0*CwUmt=y&0*fq6dQgIHg zsXLZeKEEi_?*3lPt~bV+#|nSkI1==fZfMl;$wXtl=jME7ofZnS^D}OL&ir+;9IANo zVZTd!QZdJl+=6T)fAe0FUXo7avd`(uVm4dkwl&rf7c6-k>EgK7^xU2MIsDnD6R2s; zVV@Z1oSMX(w%TLKzkhJ~QA7PrUf1hWYYRR4(9OV($!3z(bnpBxzbEr`qqKWh*R52a zvjdfiK-#5`IAHSoSd||}S$s+(NDgkPpFKgAShPO>%aM8h1b)-#IYEylm9za`_gHnE$bZ zV+lS9(CQ;Hc^O+qr1nkLo}MH4SF3{G z9OBAa75&Lo2(@yrrM;p`zgg3X;^j}cHJekOK>wpm&L!(3+u2W2?-FrlI!Q9YlUx(- zXtnUX=sS)7s=_}tOFWrP4AHw8H?+Mx*oiye)*~r zJc>38PqA8$^5AO_499JPAi2g#gTsbZzJDJqw0w`R3|^7)xI4yNyQ$->TQ{m6>w`ao z_B*20IvJ62fq*g%H-ZK2`KWm$PR;%_s@KrWw3(8SJ)7C1hUu2YJ`IuEbzFr>$WoAK zgTR=_$UK4c4ExB*0K8DTm0V*7RjD7a?2G?7O?b~{^yqwIl2M)RB111uao1D+<@Y z!AlkC)d8Z@XiIq{@G2Lms-7YLg)jM!KYJycYGrqY#UCXkrp{s?H zAbDBjQLf9s?ibRSD~Kj>%j-{V=mEGxJXkX3eX9!!DhV>ztCE z+`$Q@ZQX(aNWmX;XKKg)m7`vnN(!5(<8)F(HPzTVu|xTE(|2Hyb-_#- zS^gy~hqyg~JEMrHME$2}$&52u3yFF@2q#uef3};=+Q0~9{-(K2ZAB3LhJcY;? z7$vs0Dk;qORee#BGZRYdavE{W5ImAfb48} zRnYT_m*CcJ$~RS0F&~wEyYA{*p|G)CJ#y)s^$Jwf1SJgrcx|hXr%H@Ek)``}st+Ij zp9O5WxyUe^A7VV<0%{s2j*;&RedV_=e0s*XWd5y0fqw4N@9v#sqa&no>kwa9x&A%E z20SCg%m_shl!E6^Ae$t!cLnPHh$?)1`THi~{&j!eqUq70nZv|LPf@2{JbTks1OH;n z^iRDBSYrDgO3fOX<@p!Xz`5-9f+RC7$&YhY^qXex^wO)Y!H?TKAZ&6n7v@; z(Iw}yNS69hWl@;Fb z7i900Gtta!RFkz8=U1+Up4@&%k*$2+(eNez(xa&Qv%XFI4HsUxmc~To@#tSz7%*ig zu#&e3wX+n}&e7`uQ!0j7%8%yedy%}qjmYaW^28wqB=ZNHzijcX=X)7xpV3pIZmuPy zaeojLKB*_&`keq?F%9;PL<*-vw8mzZ(-k9Dkimcm_0i0vaKH&mlX3kJLAvA z46R*=Tk0(JaW1;NDL!J!n6XqV?)#*;l}R@Kw`0k<)~+*A9p7YBz6S#NJIQ7_;y z@wRu?n4Cwbr$aqFVq9?8$0uwgfPRhp73J9|FDqRR<;tQ(72FecI*#497-KpsgrS?G zP%6sx1q2W@z`DLG{rMorD46WM7j1Ui+`N(yeL@9uSsa##K)P=c+B@2dDH0Xcf99X6 z_x!{LBLe5vo05M@1fpkXP{(uozZT25zvg_SfY9ZAKQ|OSK6vxbrVS5v5_gx20OMbh zI_(@IItyEeCN?R%Y?8tnJ|UPVI7*R>P0miKG}-8Sk04BWYui!oi|d3U)JxaM;`u$?Dq24Sy)>8+q{*LneI48y3iRp+E(wfKh=)?{V-AIu z$#y-J+ssPL7T+lLWZWpRl5Ap6Dvr4yx9dhJ?w0S%)rO912P;;strcf{#50P9hUPn- zDi?uqa;-xYp3E#(I!h39E3-#wR(Ych?Gi5&#VcQ74RNeN?~NjDe*yIZ;Rkzq=T3U? zGCt!Gi>4NZzRBC4lW(D;;hBm3RH~HQ?1H8iim&+RnKg<|wNk2ibQ}H0Q{T+YoW%{ol0jvotp2ayTjl*3^o!U029FVo zcpGdJs#I@wzmJ}lUkPO;9c|g}o&3gXEM9$o|9I&2A6gx6WonC-pJTOLVt1|L}e z-o#i0*t<6t4_;sNXNjF`9{pgknTfN4DevN!}0HtC3a1Vnh<;7#RnS%(! ziRT^6Fn0NxUv48#Z@{sc<9l;JcAy@Y0BRu%`Ik>>Lck}g7~IhO&qt!Gc#F{U^bZcA zj((av68nsIf>y{HlqBrLQH8=L6Wmam%)ZI4E6y4x#9VX`R-gl2?I&?eS7F&zmJ*BU zi5;FCSlt|?v?~Yc9{B$5qvw&*nKP2&y7Ty;qyqIQJ@I~FokDzY@TOS(KxpSH=>V_7 z@vA5#ACtfC-dj+#@0cfxM)`kFD|gB-uoF)FrHayo&dT#YFFKH zCj-49$3fa5PKg87s!D?h=8l9FRb4w;bCR|U6OhCxl~W8*vS@0eC=5DH^emVx98vv@ zGgg7#>};;mm;MzInl6VvuZ-r3gV4@^b)KFF_a6Zt?!TU*J}dV0rT#3r*l4}I4Qq8W zW2>A$xc{bk&z}+`uv$NfmC+@wIUc+7rfdN`=Sfoce?yE@uCrj9IqDn5|BMS=RRkr5 zlj=*?cuZS!6q1R&>MUui2mRSuM+oe-zc<#or_@+NlB0Sct?nlrgn;$2!LZX|3W7Zx z>HbUSWU7aFhY@!oguR6Cj+I+CQyJFVo8Ah!(X5H{NEZ_UhcstDXmuJ(5<|ftlH0S$~;7TM42eG30x!l@%4>OGWY z!Igc~*DCcyF#aDseorcG!DX}b}VO2LdaYYmSe+^i(R6T`?n_+-s5-;02!@{N3 zMzE%_0!tyi-k)CGh_0*VdH7nrvR^R;f^$}V9m4(AD3`Sqy9+By2WN@Vg9gq_8{1*$ z-roJ_Cm0E)iXcPc-21LxQ3fMz_6LnW2$pUW?6zV$VCr^8wps!x5@n2mK?k;}ABHU;c&Syw8LZSF+v)fP*C zW^a?^F<19@h_-{swg27Z;FM<)JDW871Oo%NK(DmxmwW9zMcO-q{<-b&3_>T*pm=Wm z3hO6t%Px{kmLs7#cuIn;P*aAEsy}v)HJ<{>Ey+$vmTR744Tuw|^!-SycnR<#zWzm9 zSXz5;UEy#3zr8u>Xv99wsAXCNPWP-s0c5WZl(g``f4!weo~pZtJPsBYmO*g^Ecd*M zygP+_HTC5j(IGZ}qY7!_h8c!TM6d<7+u!J8_q(ueBJO8HI~5qH?e$0J{^L~g)sLkt zhr5Rfgl8nNF}8!^0V&BWX3nH>_eI}Vb5~cE3~~N;T2f_Ghs{i7*LyL~6Mc1Jx+c5~fyExKeAUhOI-yc=cNChBZGYZNg`jJF% zfXx^l%7HcmIeS}_exMQv?i|&bgoA%>2mkZq^)hDV*7t^P_tKVnW53bnrK&H+s!QTp z$6({r2aw8q1V&s}r}AA~w;G=vVn@^ljV7>&W{1$9J3py?T#qTysaL2wI>6v( zjal4}GJ!4y5nW$0)Min}`4$CY*z>dep407g!VVHK3Tb@yGckBl8n`v;w5Kzgi{8m? z+15E8e>Wf@&L8kN_c7x7CQO@73a3u1=PO^^=kS2F2J3rPi&KH*v*b@d`JtNNn!xJj zPC9(PxVA#)DU)CUwwN;%B+fin4S!NTp6N|jI9 zQ8<2c@<7|Q;B8%0K`!l$xHlsRR?F2VOJ3iJ0{z=ecEfTTu5V14Y8?Al#S5g9M9oj zfTr<3Uq$>YQ?GMHJSY!Cz~(#=W$4dOh;hMXvix@aoAY%k*C44#bUoE&(nfkih-d&_1OrYzchR$&AThiq9AmUc zG~qdunS-&EFk_M=^g5c+1oX59auVZ&c*Rz7KPhyQ>F1y@pcaX{<yq&SsRh>N;FZs3 zg#TmzvOu!iUPc8k`t@SsNi9aY&GV0QlsxCZ#SOL5;)cC^Mcv$4wUMkHF7E!7(g8Dc~#Y59(ptB;6oI}^9$S@h=RRE21^0;T0)m@WW;7ZDn z7Z<-=@Z8tn<(HTd8;A~)(!;)ob=pJzzk{=k@+aDAH)pHcI@H~AEj39KcQWRSi&Ze4 zbx?eSMlnyT43O`s-BwJ-uuo=xW$Zy&&!#Bw$8j%%L@igsFJ@!mLGuMw7Lg>AwY=b~ zljhw~0l9g{`H&(5HU#txNqkj14Ow9t&33x|djjf?dR94`5YUr-@+ihh;N$?vDjszR zP4b}qo-9psuQ@AMGATbLy?D9fh&>HSzggtjPJGAc(pXDCIQl!~X-jv_frib?;O^)Q z_6733=;|TF{^UZ6&v|4iPiHWsC3;Yb(~$$qxC%0w>y~0*6c6?`cd+d2z0bdK_~JEf zllD-wDhO~|H=b@3GFHaseufUA#1BkW;&SZ$c_%ynwW{**1lnSR`rM_=PUv7H@x{FO zah0H14)HyB+JC0Mqk;jZfsWpagZ))#)n4@a8i44bQqdVFgn@p}@nd-Ka|F`iw*A?U zj~mq|v!9U@>vESeqAv=A(8Hq{ulb4jsszPa+eM{Iv-orui8k}noafh39ob^`IR0`+ z-H{?a2Kj1YS&$77s;W5Azam80Fstdz5j1ka1kH2Z6xABNz6{CXdhOr8@7^LjkI5E2 zA4*fd2)i(fsbx)bDGS~tM4CTRgERy-dR_uO<0oYXfBJ0(X9%=&b%?<&4q98N zF6c<{PxSDi;CtGEY6V?vBQstBApn=t5htl9fFE2xX;)7KP~cK6EnqV7FT@;@ zI`Jnr1>xP_`! zxwQH1Tn^mmK{F`yM;e*Te4=jf0oR=VS5uG0NG=>y5U0*P-TPk@ckd?iVsjPqP;tJVVb9?fYw7#mZ z)iFOgjklRYJ%Db9L0jJcDNwd6XS z7$&b<*bfo?KW~>(BP=DWG+xgxXmyYSP|{1P-DE zmK{VsqtUzB5nEeRa%Jb|hd%*L%+J%MrKNT8Dl`Knh5n0y@8L_yiv;HTwGj!OzC=|K!bj|?Nadl`t^0l z{M$qB&uN>P3A0(BKU*$EvT<^%myZpjI=u+Lwf%PcYfLG4cJr8|V|~g~Lw_$kK^~Xz zTNWI^oc>G7q{$rqZdpZ1vl{6ZLs}b^VM7+;+Jz86u#@>k6swX%Ej5$Flbqm@_`pyoN8F2HZY>JapHt z_||S<^0bKI<7(@1XZy~zO39>PXi&QJb)$&cHhU%ui+|-2Sr4KbQYxmUiNaJ<>$dru zV1UVltd*5b{{lPtL*LJN$s39gzjxv2Xk*52&e3|Y;3UkR zBpvA91&6>P(S(zt%Asz<6spH^iA@b1lpr~6l`$AJrT=xJ%Oyq;nevAq05N!XERX|L zj^jV)7k{HW`b=pUwnEJ}#glZK}`QQwDmY}iUVv~1&*X&lN#gsO^@ zyBd1aoI+(@7R#VeODwE;5kSYq<1L$znU0|$gg(#>*5c+x1Z=}HKh9N7Cx-+eU&bV> zKlNQ5TOHKAVK2yU2DIU_{Yru5+`Jg1)YMcNS667HG*LgUop}lVQ9*2+&?p`nKtNw+ z*;%eMo<~7hvb20DW@cvgA=Z>DgdD&jeYD{0MV5-FpVGaZzuZ-w1BlqGEm6KbD+#~% zw*MGL6d7`H#+vBIj4hE5bcTmbI?U-t zDS|@pFtOR~UlJHy$%yP=#k3xh`7!+x*JwaYf)&?$F;_F!fRA%CduO#{W7nB*`E-^1 ze8D%@vvJ`H)d?s>P?eAkMQzIfBGM9{uH7z-ri>bxE^jbHy2YhXy?Uez+2B4U7qQeQk?Wmwb#;v@#Cm&p*a{h9 zdWnhGenRhUHko6lk~T9d#^@a{^;|+YAW6YE|Hj6$fe4Vb9a6!NoxKZJQE50`yTWBH z+=-@k%BNeH+fm;Dsr^~Q$lS%u9Cr*AR%`%(oYP0d?bin1h=b!tS7s$*bapU&y{{ZT z>;;5;Naw`~`GNejre$mUOq_yV+`|Z#O1;vk&&+JAS>8*3D|eK5y{f^RC0eko*;!ad zBZ&q_h`u>w8><>`;s7*hyyKs;vk$SO2df}U1!!n!`hgv3#I*cn%+GL&BI1hEj)>6H zPn=|cv`H*D%xm$bVCn#+E$yGOEPPzV!tAfE9UMF$`~VQ;oVV+cIeiHEJ3;g?^zlnx`F9y^?e@Auf*D*;hQwFeqDs{iD{Gpe9zD;s}Jen3j5@%)UgEYZXJK zokaql0D_WLPvH}fckAU54YQ*E=O;MtkpYFO!&DPiR0%x1Mt(Ng3#rV#Umxfy%Gs&S z!uyEKm3Ab_P>Nr{vrPSJ^wpD=j-{_}Wo8-GS)db9!AR~9I5&ktp?@-zHc5y`(tph* zq#>3<^g22J|;6AH$Ph<~Kx%YP;}F?7;x4F(V!8M+7w~{zVsw z?3|I_6f~-(fLG*F7DW9a&lyUyGckDs%48gDA|i(Tzzz&HYmDCTyQzs=!&tv@8^K%7 z`VS274QbRIHwP4Kv#*Hd7H-_g|fBF{hXGj!-7V$KAEv`za_WOpNv2b0S~THmvQLvv6}iTat{K zg(S-+vDdNhX;s&T!p}~iv>mq{#GI6$hKEp{cTl^~5MlLguLah=4DLhXa=SeyJ^& z?;xp4e^b7MRbi()ug966jTXekO(2^+0jedzsF8AH+xhDd;*Ia}<}g5W>&My4Qy zt0P2=`=*258n5IrEwfb)H$Oj*l>nn45ah%Mw9Cr=++JV!s|>^&5HAHVxOXc44H-gz zc)blq4G104Xh~|Klx$<-aDA+OHwRt; zKCSHURf>A$w~sj)G05k-vuNh1mMYbv;1NJDh^JCQKpl_3fiRbsWe>2 z#Ms!Xg;stZGCUs+C`sWn9EP>InOSvnQ0>1NzbRO&bjt~O(ZE?lshDs0=fEIVVS4=cee5R)zlP!$u;H7}=ki>t5fw&D5 z;?g4d?W_B@&TFZVPkS+5fP{wKAT1;-*w6#wmC6y<8+|W%RM1VRW!~u{uY@LV=&5)2?^<32S3#~LZ zwAiTvfj?X2xmbl21J6R*w}S@)1?*o$$V_z7tH&6nlSp92H2CFZ%CxcB-KsPl>2L<0 zKYz|6S{k59m6KF&e^2M+YnZUz+SYd7Pp7pvj!|oEVPWwO7>*%Dm`eKcCBYSAM>6lm zYp*a!3HjvsE+*!QMFzj5*M*asTJi@7-T=J~8IgdU^vV>MR+ zz(^67oa93a@Q`650Ss;^88XseViF=b`~MXAL@$24%LA_0RaC4$EC)Oz=!1OP^N*!2 z+B75TuFoqSeTXEan!A+WHN=`QE##_8M8ujbVFL5JGIAn;PoiIJiJ=KRgRHJ@NmhB+yVAS_g!s^zt`pI-+lY^j81(g`Y4fs1J9<+{b+a?HRL)qX6b zND->+x^v-$H$W@xpyMfXk-4bzL&Sy#7YiP!Cy0ixRV#|AL<|8t3@KkKYiNID7>IgI zL`}J?G;U_@a5waY_U<7g$mhk{o8841Kp+k!uWbC(R`f&S(;|)Zmth8)= zr?(o8OfNls7ad3#z<__e?m+QEA2PoM!Durg0?|>+cSJ1)6<<(@jF3F_ZWBepTmd5a z4{K?bY>}BSZ*OnMe*JPYrAq<^h%N2*-pGDLXwU>D5FxN__9mGtsw)AKf1w_?I~f_x ze^&Tfo=j)u3DBvixaBQFMpiQ6AduG9&WM+_u#ZWl5u>3g@F9?o=&<4}EicEKQnf@# zcpsA`4A50^IWtVDN4{4_GYiG2bRdxW@GGYekRW5=l10(f)bz*I#Umm%6CcewrwZW* zBt{`>$lpn)1b<3qjiy=A1E@-36otoGSwx?X&9OB54i)v=XE8V|>J8gxJhiklumKcc z{$YQsh}~8q!3yUvh_NfWncqCe{mjDB_f@9=Zebco?uu1oufF&`WXObV4aWpaWM^=q zOJf07W*9{eox-5O@rvfJ$Pg2b1jiy>0^)fzs?vVeIXqAcUPEhdN9YTpp9Gzw5a8qQ zKZ+DJlog{0)ZJnthc`0z&c-XnLxIIsg+c?^8z|}LSuR6~w#1khKpv1X#@y$C>rH+z z{)3iupugSWS->cAqkFX=xSE}zk#0*YOnk@1pM89lVWw98EgR8s8q3ABP;=rijDt2ot-QPkzAz(3JO#Yf5YDlgU7uNK4FbqQ#DwJc*)LaL5d%_c ze=*?_+sdeXm+{sj!Q?=}+>wKo6bNN~$)TpJ;VO~Aq9;=iGW6IXSifB+N>$g@u=Ars zSS#EuHs8i@zIP0%o14!>4flV1JY4nXL8F1+QK{oL9~j_g*5qb;rSzRaAw3&GC8(^s z2+p#`F}Kwq`(x>aGuqmY;-VfTI#tdZs7Xx zmA=LB~T-g)>jeWA6$fwA+b{@@=-+Y{IS_MHr9(f<}TvP(Vg`#CLTnkn;}!>M1* z5jmq^2jAMxHr1pP8?>pZsZyneeASTteCqU#z)Qy6@USLu?&S9nofLNLF6C*Kc<2lk zvj@(2Y`i)vlE=E}HZKa=TIbzCMU!xjk-mOTvi6SHA^w*CS$`yYTr^u596$!Te+#UJyKC2|O$TNR;J^^pSJqNcYJCqfe=uVA=kb-Sd5en@ELeRj^h zP>cn{1v!M4*6`jqD?(9aJWEkJI6gksaBkigF61u)3O776U(huw_&*0gK}JQo?!8Ia F{{RU`S|b1e literal 0 HcmV?d00001 From 5fb2840e5df69ea0895ed5821ced6d4bc266a328 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Fri, 5 Sep 2025 10:49:12 -0500 Subject: [PATCH 005/144] npx prisma db pull --- package-lock.json | 349 +++++++++++++++++- package.json | 7 + .../prisma/migrations/00_init/migration.sql | 156 ++++++++ src/lib/prisma/schema.prisma | 142 +++++++ 4 files changed, 649 insertions(+), 5 deletions(-) create mode 100644 src/lib/prisma/migrations/00_init/migration.sql create mode 100644 src/lib/prisma/schema.prisma diff --git a/package-lock.json b/package-lock.json index 4652dab2..e5972b1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,10 @@ "": { "name": "appbuilder-buildengine-api", "version": "0.0.1", + "dependencies": { + "@prisma/client": "^6.15.0", + "prisma": "^6.15.0" + }, "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", @@ -804,6 +808,85 @@ "dev": true, "license": "MIT" }, + "node_modules/@prisma/client": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.15.0.tgz", + "integrity": "sha512-wR2LXUbOH4cL/WToatI/Y2c7uzni76oNFND7+23ypLllBmIS8e3ZHhO+nud9iXSXKFt1SoM3fTZvHawg63emZw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.15.0.tgz", + "integrity": "sha512-KMEoec9b2u6zX0EbSEx/dRpx1oNLjqJEBZYyK0S3TTIbZ7GEGoVyGyFRk4C72+A38cuPLbfQGQvgOD+gBErKlA==", + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.16.12", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.15.0.tgz", + "integrity": "sha512-y7cSeLuQmyt+A3hstAs6tsuAiVXSnw9T55ra77z0nbNkA8Lcq9rNcQg6PI00by/+WnE/aMRJ/W7sZWn2cgIy1g==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.15.0.tgz", + "integrity": "sha512-opITiR5ddFJ1N2iqa7mkRlohCZqVSsHhRcc29QXeldMljOf4FSellLT0J5goVb64EzRTKcIDeIsJBgmilNcKxA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.15.0", + "@prisma/engines-version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", + "@prisma/fetch-engine": "6.15.0", + "@prisma/get-platform": "6.15.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb.tgz", + "integrity": "sha512-a/46aK5j6L3ePwilZYEgYDPrhBQ/n4gYjLxT5YncUTJJNRnTCVjPF86QdzUOLRdYjCLfhtZp9aum90W0J+trrg==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.15.0.tgz", + "integrity": "sha512-xcT5f6b+OWBq6vTUnRCc7qL+Im570CtwvgSj+0MTSGA1o9UDSKZ/WANvwtiRXdbYWECpyC3CukoG3A04VTAPHw==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.15.0", + "@prisma/engines-version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", + "@prisma/get-platform": "6.15.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.15.0.tgz", + "integrity": "sha512-Jbb+Xbxyp05NSR1x2epabetHiXvpO8tdN2YNoWoA/ZsbYyxxu/CO/ROBauIFuMXs3Ti+W7N7SJtWsHGaWte9Rg==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.15.0" + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "28.0.6", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", @@ -1198,7 +1281,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "dev": true, "license": "MIT" }, "node_modules/@sveltejs/acorn-typescript": { @@ -1982,6 +2064,34 @@ "node": ">=8" } }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2013,7 +2123,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2035,6 +2144,15 @@ "node": ">=18" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2079,6 +2197,21 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -2162,6 +2295,27 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2179,6 +2333,37 @@ "dev": true, "license": "MIT" }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/effect": { + "version": "3.16.12", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz", + "integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -2477,6 +2662,34 @@ "node": ">=0.10.0" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2645,6 +2858,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2822,7 +3052,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -3334,6 +3563,37 @@ "dev": true, "license": "MIT" }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/nypm": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz", + "integrity": "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==", + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.2.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3424,6 +3684,18 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3444,6 +3716,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3705,6 +3988,31 @@ } } }, + "node_modules/prisma": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.15.0.tgz", + "integrity": "sha512-E6RCgOt+kUVtjtZgLQDBJ6md2tDItLJNExwI0XJeBc1FKL+Vwb+ovxXxuok9r8oBgsOXBA33fGDuE/0qDdCWqQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.15.0", + "@prisma/engines": "6.15.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3715,6 +4023,22 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3736,11 +4060,20 @@ ], "license": "MIT" }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -4105,6 +4438,12 @@ "node": ">=18" } }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -4175,7 +4514,7 @@ "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 7e38e844..0ab76135 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,9 @@ "format": "prettier --write .", "lint": "prettier --check . && eslint ." }, + "prisma": { + "schema": "src/lib/prisma/schema.prisma" + }, "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", @@ -34,5 +37,9 @@ "typescript": "^5.0.0", "typescript-eslint": "^8.20.0", "vite": "^7.0.4" + }, + "dependencies": { + "@prisma/client": "^6.15.0", + "prisma": "^6.15.0" } } diff --git a/src/lib/prisma/migrations/00_init/migration.sql b/src/lib/prisma/migrations/00_init/migration.sql new file mode 100644 index 00000000..74a49e56 --- /dev/null +++ b/src/lib/prisma/migrations/00_init/migration.sql @@ -0,0 +1,156 @@ +-- CreateTable +CREATE TABLE `build` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `job_id` INTEGER NOT NULL, + `status` VARCHAR(255) NULL, + `result` VARCHAR(255) NULL, + `error` VARCHAR(2083) NULL, + `created` DATETIME(0) NULL, + `updated` DATETIME(0) NULL, + `channel` VARCHAR(255) NULL, + `version_code` INTEGER NULL, + `artifact_url_base` VARCHAR(2083) NULL, + `artifact_files` VARCHAR(4096) NULL, + `build_guid` VARCHAR(255) NULL, + `console_text_url` VARCHAR(255) NULL, + `codebuild_url` VARCHAR(255) NULL, + `targets` VARCHAR(255) NULL, + `environment` TEXT NULL, + + INDEX `fk_build_job_id`(`job_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `client` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `access_token` VARCHAR(255) NOT NULL, + `prefix` VARCHAR(4) NOT NULL, + `created` DATETIME(0) NULL, + `updated` DATETIME(0) NULL, + + INDEX `idx_accesS_token`(`access_token`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `email_queue` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `to` VARCHAR(255) NOT NULL, + `cc` VARCHAR(255) NULL, + `bcc` VARCHAR(255) NULL, + `subject` VARCHAR(255) NOT NULL, + `text_body` TEXT NULL, + `html_body` TEXT NULL, + `attempts_count` BOOLEAN NULL, + `last_attempt` DATETIME(0) NULL, + `created` DATETIME(0) NULL, + `error` VARCHAR(255) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `job` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `request_id` VARCHAR(255) NOT NULL, + `git_url` VARCHAR(2083) NOT NULL, + `app_id` VARCHAR(255) NOT NULL, + `publisher_id` VARCHAR(255) NOT NULL, + `created` DATETIME(0) NULL, + `updated` DATETIME(0) NULL, + `client_id` INTEGER NULL, + `existing_version_code` INTEGER NULL DEFAULT 0, + `jenkins_build_url` VARCHAR(1024) NULL, + `jenkins_publish_url` VARCHAR(1024) NULL, + + INDEX `fk_job_client_id`(`client_id`), + INDEX `idx_request_id`(`request_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `migration` ( + `version` VARCHAR(180) NOT NULL, + `apply_time` INTEGER NULL, + + PRIMARY KEY (`version`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `operation_queue` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `operation` VARCHAR(255) NOT NULL, + `operation_object_id` INTEGER NULL, + `operation_parms` VARCHAR(2048) NULL, + `attempt_count` INTEGER NOT NULL, + `last_attempt` DATETIME(0) NULL, + `try_after` DATETIME(0) NULL, + `start_time` DATETIME(0) NULL, + `last_error` VARCHAR(2048) NULL, + `created` DATETIME(0) NULL, + `updated` DATETIME(0) NULL, + + INDEX `idx_start_time`(`start_time`), + INDEX `idx_try_after`(`try_after`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `project` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `status` VARCHAR(255) NULL, + `result` VARCHAR(255) NULL, + `error` VARCHAR(2083) NULL, + `url` VARCHAR(1024) NULL, + `user_id` VARCHAR(255) NULL, + `group_id` VARCHAR(255) NULL, + `app_id` VARCHAR(255) NULL, + `project_name` VARCHAR(255) NULL, + `language_code` VARCHAR(255) NULL, + `publishing_key` VARCHAR(1024) NULL, + `created` DATETIME(0) NULL, + `updated` DATETIME(0) NULL, + `client_id` INTEGER NULL, + + INDEX `fk_project_client_id`(`client_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `release` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `build_id` INTEGER NOT NULL, + `status` VARCHAR(255) NULL, + `created` DATETIME(0) NULL, + `updated` DATETIME(0) NULL, + `result` VARCHAR(255) NULL, + `error` VARCHAR(2083) NULL, + `channel` VARCHAR(255) NOT NULL, + `title` VARCHAR(30) NULL, + `defaultLanguage` VARCHAR(255) NULL, + `promote_from` VARCHAR(255) NULL, + `build_guid` VARCHAR(255) NULL, + `console_text_url` VARCHAR(255) NULL, + `codebuild_url` VARCHAR(255) NULL, + `targets` VARCHAR(255) NULL, + `environment` TEXT NULL, + `artifact_url_base` VARCHAR(255) NULL, + `artifact_files` VARCHAR(255) NULL, + + INDEX `fk_release_build_id`(`build_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `build` ADD CONSTRAINT `fk_build_job_id` FOREIGN KEY (`job_id`) REFERENCES `job`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE `job` ADD CONSTRAINT `fk_job_client_id` FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE `project` ADD CONSTRAINT `fk_project_client_id` FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE `release` ADD CONSTRAINT `fk_release_build_id` FOREIGN KEY (`build_id`) REFERENCES `build`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; + diff --git a/src/lib/prisma/schema.prisma b/src/lib/prisma/schema.prisma new file mode 100644 index 00000000..2cef3318 --- /dev/null +++ b/src/lib/prisma/schema.prisma @@ -0,0 +1,142 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +model build { + id Int @id @default(autoincrement()) + job_id Int + status String? @db.VarChar(255) + result String? @db.VarChar(255) + error String? @db.VarChar(2083) + created DateTime? @db.DateTime(0) + updated DateTime? @db.DateTime(0) + channel String? @db.VarChar(255) + version_code Int? + artifact_url_base String? @db.VarChar(2083) + artifact_files String? @db.VarChar(4096) + build_guid String? @db.VarChar(255) + console_text_url String? @db.VarChar(255) + codebuild_url String? @db.VarChar(255) + targets String? @db.VarChar(255) + environment String? @db.Text + job job @relation(fields: [job_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_build_job_id") + release release[] + + @@index([job_id], map: "fk_build_job_id") +} + +model client { + id Int @id @default(autoincrement()) + access_token String @db.VarChar(255) + prefix String @db.VarChar(4) + created DateTime? @db.DateTime(0) + updated DateTime? @db.DateTime(0) + job job[] + project project[] + + @@index([access_token], map: "idx_accesS_token") +} + +model email_queue { + id Int @id @default(autoincrement()) + to String @db.VarChar(255) + cc String? @db.VarChar(255) + bcc String? @db.VarChar(255) + subject String @db.VarChar(255) + text_body String? @db.Text + html_body String? @db.Text + attempts_count Boolean? + last_attempt DateTime? @db.DateTime(0) + created DateTime? @db.DateTime(0) + error String? @db.VarChar(255) +} + +model job { + id Int @id @default(autoincrement()) + request_id String @db.VarChar(255) + git_url String @db.VarChar(2083) + app_id String @db.VarChar(255) + publisher_id String @db.VarChar(255) + created DateTime? @db.DateTime(0) + updated DateTime? @db.DateTime(0) + client_id Int? + existing_version_code Int? @default(0) + jenkins_build_url String? @db.VarChar(1024) + jenkins_publish_url String? @db.VarChar(1024) + build build[] + client client? @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_job_client_id") + + @@index([client_id], map: "fk_job_client_id") + @@index([request_id], map: "idx_request_id") +} + +model migration { + version String @id @db.VarChar(180) + apply_time Int? +} + +model operation_queue { + id Int @id @default(autoincrement()) + operation String @db.VarChar(255) + operation_object_id Int? + operation_parms String? @db.VarChar(2048) + attempt_count Int + last_attempt DateTime? @db.DateTime(0) + try_after DateTime? @db.DateTime(0) + start_time DateTime? @db.DateTime(0) + last_error String? @db.VarChar(2048) + created DateTime? @db.DateTime(0) + updated DateTime? @db.DateTime(0) + + @@index([start_time], map: "idx_start_time") + @@index([try_after], map: "idx_try_after") +} + +model project { + id Int @id @default(autoincrement()) + status String? @db.VarChar(255) + result String? @db.VarChar(255) + error String? @db.VarChar(2083) + url String? @db.VarChar(1024) + user_id String? @db.VarChar(255) + group_id String? @db.VarChar(255) + app_id String? @db.VarChar(255) + project_name String? @db.VarChar(255) + language_code String? @db.VarChar(255) + publishing_key String? @db.VarChar(1024) + created DateTime? @db.DateTime(0) + updated DateTime? @db.DateTime(0) + client_id Int? + client client? @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_project_client_id") + + @@index([client_id], map: "fk_project_client_id") +} + +model release { + id Int @id @default(autoincrement()) + build_id Int + status String? @db.VarChar(255) + created DateTime? @db.DateTime(0) + updated DateTime? @db.DateTime(0) + result String? @db.VarChar(255) + error String? @db.VarChar(2083) + channel String @db.VarChar(255) + title String? @db.VarChar(30) + defaultLanguage String? @db.VarChar(255) + promote_from String? @db.VarChar(255) + build_guid String? @db.VarChar(255) + console_text_url String? @db.VarChar(255) + codebuild_url String? @db.VarChar(255) + targets String? @db.VarChar(255) + environment String? @db.Text + artifact_url_base String? @db.VarChar(255) + artifact_files String? @db.VarChar(255) + build build @relation(fields: [build_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_release_build_id") + + @@index([build_id], map: "fk_release_build_id") +} From dbddea5f82313b4c65a9ace30f117908bbd6d63a Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Fri, 5 Sep 2025 11:31:54 -0500 Subject: [PATCH 006/144] MySQL docker compose --- deployment/development/docker-compose.yml | 15 +++++ run | 81 +++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 deployment/development/docker-compose.yml create mode 100755 run diff --git a/deployment/development/docker-compose.yml b/deployment/development/docker-compose.yml new file mode 100644 index 00000000..7940125f --- /dev/null +++ b/deployment/development/docker-compose.yml @@ -0,0 +1,15 @@ +volumes: + mysql-data: + +services: + db: + image: mysql:5.6 + environment: + MYSQL_DATABASE: development + MYSQL_USER: appbuilder + MYSQL_PASSWORD: 1234 + MYSQL_ROOT_PASSWORD: 1234 + ports: + - "3306:3306" + volumes: + - mysql-data:/var/lib/mysql/data diff --git a/run b/run new file mode 100755 index 00000000..7e4efb58 --- /dev/null +++ b/run @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# +# A script to make working with docker/compose to have +# less typing. +# +# For local development only. + +COMPOSE="docker compose -f deployment/development/docker-compose.yml --project-directory . -p appbuilder-buildengine-api" + +function runstuff { + # First arg + given_command=$1 + # The rest of the args + arguments=${@:2} + + case $given_command in + # docker compose proxy + dc) ${COMPOSE} $arguments;; + up:build) ${COMPOSE} up --build $arguments ;; + up) ${COMPOSE} up $arguments ;; + local) + runstuff dc up -d db + ;; + down) + ${COMPOSE} down $arguments + ;; + build) ${COMPOSE} build $arguments ;; + + bash) ${COMPOSE} run --rm $arguments bash;; + restart) ${COMPOSE} stop $arguments && ${COMPOSE} start $arguments;; + + db:migrate) + npx prisma migrate dev --schema src/lib/prisma/schema.prisma + ;; + + db:reset) + npx prisma migrate reset --force --schema src/lib/prisma/schema.prisma + ;; + + ################ + # Host-Machine Utils + + # Don't name any folders bin, obj, tmp, dist, or node_modules + clean:all) + echo "Cleaning..." + shopt -s globstar + rm -rf **/out/ && \ + rm -rf **/dist && \ + rm -rf **/node_modules && \ + rm -rf **/tmp + ;; + + *) print_help;; + esac +} + +function print_help { + + echo "" + echo "Available Commands:" + echo "" + echo "dc : short for docker compose" + echo "up:build : starts the docker compose services and builds images" + echo "up : starts the docker compose services" + echo "down : stops the docker compose services" + echo "local : starts external services in docker for local development" + echo "" + echo "bash : drop into a bash shell in a temporary docker compose service" + echo "restart : restart a specific docker compose service" + echo "" + echo "db:migrate : run prisma migrate dev command" + echo "db:reset : run prisma migrate reset command" + echo "" + echo "clean:all : recursively removes out, dist, node_modules, and tmp directories" +} + +if [ $1 ]; then + runstuff $* +else + print_help +fi From 6c66cee6c224cb4dc7187ecf805e9e4b19295515 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Fri, 5 Sep 2025 11:53:54 -0500 Subject: [PATCH 007/144] Update db version --- deployment/development/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/development/docker-compose.yml b/deployment/development/docker-compose.yml index 7940125f..c064ce5e 100644 --- a/deployment/development/docker-compose.yml +++ b/deployment/development/docker-compose.yml @@ -3,7 +3,7 @@ volumes: services: db: - image: mysql:5.6 + image: mariadb:10.11 environment: MYSQL_DATABASE: development MYSQL_USER: appbuilder From c643df2cc9661606308656fd5113fbfb2522da6d Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 8 Sep 2025 10:20:02 -0500 Subject: [PATCH 008/144] Add image for ssh tunneling to staging server --- deployment/development/Dockerfile.stg-tunnel | 2 ++ deployment/development/docker-compose.yml | 11 +++++++++++ run | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 deployment/development/Dockerfile.stg-tunnel diff --git a/deployment/development/Dockerfile.stg-tunnel b/deployment/development/Dockerfile.stg-tunnel new file mode 100644 index 00000000..4bd8d102 --- /dev/null +++ b/deployment/development/Dockerfile.stg-tunnel @@ -0,0 +1,2 @@ +FROM alpine +RUN apk --no-cache add openssh-client bash \ No newline at end of file diff --git a/deployment/development/docker-compose.yml b/deployment/development/docker-compose.yml index c064ce5e..5650b616 100644 --- a/deployment/development/docker-compose.yml +++ b/deployment/development/docker-compose.yml @@ -13,3 +13,14 @@ services: - "3306:3306" volumes: - mysql-data:/var/lib/mysql/data + + stg-tunnel: + build: + context: ./deployment/development + dockerfile: Dockerfile.stg-tunnel + ports: + - "8443:8443" + # yikes + command: bash -c "cp -r /root/ssh /root/.ssh; chmod -R 600 /root/.ssh; ssh -N -L \*:8443:localhost:\$(ssh aps-stg \"docker ps --format '{{.Names}} {{.Ports}}' | grep -E 'buildengine.*web' | awk -F '->' '{print \\$1}' | awk -F ':' '{print \\$2}'\") aps-stg" + volumes: + - ~/.ssh:/root/ssh diff --git a/run b/run index 7e4efb58..230af855 100755 --- a/run +++ b/run @@ -19,7 +19,7 @@ function runstuff { up:build) ${COMPOSE} up --build $arguments ;; up) ${COMPOSE} up $arguments ;; local) - runstuff dc up -d db + runstuff dc up -d db stg-tunnel ;; down) ${COMPOSE} down $arguments From a1090813dff2f38ce598a6129caf68ad8e9f25da Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 8 Sep 2025 10:20:30 -0500 Subject: [PATCH 009/144] Add some components from S2 --- src/lib/components/Pagination.svelte | 64 ++++++++++++ src/lib/components/SortTable.svelte | 140 +++++++++++++++++++++++++++ src/lib/icons/ArrowDownIcon.svelte | 10 ++ src/lib/icons/ArrowUpIcon.svelte | 10 ++ src/lib/icons/index.ts | 4 + 5 files changed, 228 insertions(+) create mode 100644 src/lib/components/Pagination.svelte create mode 100644 src/lib/components/SortTable.svelte create mode 100644 src/lib/icons/ArrowDownIcon.svelte create mode 100644 src/lib/icons/ArrowUpIcon.svelte create mode 100644 src/lib/icons/index.ts diff --git a/src/lib/components/Pagination.svelte b/src/lib/components/Pagination.svelte new file mode 100644 index 00000000..2e348941 --- /dev/null +++ b/src/lib/components/Pagination.svelte @@ -0,0 +1,64 @@ + + +{#snippet button(index: number)} + +{/snippet} + +
    + {#if pageCount > 1} +
    + + {@render button(0)} + {#if collapse} + {#if page > 3} + + {:else} + {@render button(1)} + {/if} + {#each Array.from({ length: 3 }) as _, i} + {@render button(index(i, page))} + {/each} + {#if page < pageCount - 4} + + {:else} + {@render button(pageCount - 2)} + {/if} + {:else} + {#each Array.from({ length: pageCount - 2 }) as _, i} + {@render button(i + 1)} + {/each} + {/if} + {@render button(pageCount - 1)} + +
    + {/if} +
    diff --git a/src/lib/components/SortTable.svelte b/src/lib/components/SortTable.svelte new file mode 100644 index 00000000..ca57269b --- /dev/null +++ b/src/lib/components/SortTable.svelte @@ -0,0 +1,140 @@ + + + +
    + + + + {#each columns as c} + + {/each} + + + + {#each data as d} + {@render row(d)} + {/each} + +
    { + if (c.compare) { + sortColByDirection(c); + } + }} + > + +
    +
    + + diff --git a/src/lib/icons/ArrowDownIcon.svelte b/src/lib/icons/ArrowDownIcon.svelte new file mode 100644 index 00000000..bd0bba2e --- /dev/null +++ b/src/lib/icons/ArrowDownIcon.svelte @@ -0,0 +1,10 @@ + + + + diff --git a/src/lib/icons/ArrowUpIcon.svelte b/src/lib/icons/ArrowUpIcon.svelte new file mode 100644 index 00000000..fb9ad43a --- /dev/null +++ b/src/lib/icons/ArrowUpIcon.svelte @@ -0,0 +1,10 @@ + + + + diff --git a/src/lib/icons/index.ts b/src/lib/icons/index.ts new file mode 100644 index 00000000..1799b70c --- /dev/null +++ b/src/lib/icons/index.ts @@ -0,0 +1,4 @@ +import ArrowDownIcon from './ArrowDownIcon.svelte'; +import ArrowUpIcon from './ArrowUpIcon.svelte'; + +export { ArrowDownIcon, ArrowUpIcon }; From 5ba6549ab4903bac86f2277813a3e447f8a95796 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 8 Sep 2025 10:41:51 -0500 Subject: [PATCH 010/144] Populate clients page TODO: - pagination - CRUD --- src/lib/components/SortTable.svelte | 6 +-- src/lib/server/prisma.ts | 3 ++ src/lib/utils/time.ts | 17 ++++++ src/routes/client-admin/+page.server.ts | 6 +++ src/routes/client-admin/+page.svelte | 70 ++++++++++++++++++++++++- 5 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 src/lib/server/prisma.ts create mode 100644 src/lib/utils/time.ts create mode 100644 src/routes/client-admin/+page.server.ts diff --git a/src/lib/components/SortTable.svelte b/src/lib/components/SortTable.svelte index ca57269b..4511b600 100644 --- a/src/lib/components/SortTable.svelte +++ b/src/lib/components/SortTable.svelte @@ -24,7 +24,7 @@ /** If this is true, will defer sorting to the server instead */ serverSide?: boolean; onSort?: (field: string, direction: 'asc' | 'desc') => void; - row: Snippet<[RowItem]>; + row: Snippet<[RowItem, number]>; } let { @@ -104,8 +104,8 @@ - {#each data as d} - {@render row(d)} + {#each data as d, i} + {@render row(d, i)} {/each} diff --git a/src/lib/server/prisma.ts b/src/lib/server/prisma.ts new file mode 100644 index 00000000..99b683f4 --- /dev/null +++ b/src/lib/server/prisma.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from "@prisma/client"; + +export const prisma = new PrismaClient(); \ No newline at end of file diff --git a/src/lib/utils/time.ts b/src/lib/utils/time.ts new file mode 100644 index 00000000..df2acae4 --- /dev/null +++ b/src/lib/utils/time.ts @@ -0,0 +1,17 @@ +type DateType = Date | string | null; + +export function getTimeDateString(date: DateType): string { + if (typeof date === 'string') { + date = new Date(date); + } + // using en-US as locale until we need to support other locales + return `${date?.toLocaleDateString('en-US') ?? '-'} ${ + date + ?.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + hour12: true + }) + .replace(' ', '\xa0') ?? '' + }`; +} \ No newline at end of file diff --git a/src/routes/client-admin/+page.server.ts b/src/routes/client-admin/+page.server.ts new file mode 100644 index 00000000..4d582eba --- /dev/null +++ b/src/routes/client-admin/+page.server.ts @@ -0,0 +1,6 @@ +import { prisma } from '$lib/server/prisma'; +import type { PageServerLoad } from './$types'; + +export const load = (async () => { + return { clients: await prisma.client.findMany() }; +}) satisfies PageServerLoad; diff --git a/src/routes/client-admin/+page.svelte b/src/routes/client-admin/+page.svelte index 154b3734..1b2ff597 100644 --- a/src/routes/client-admin/+page.svelte +++ b/src/routes/client-admin/+page.svelte @@ -1,2 +1,68 @@ - -Under construction \ No newline at end of file + + +
    + +
  • Home
  • +
  • Clients
  • +
    +

    Clients

    + + 0 + }, + { + id: 'token', + header: 'Access Token' + }, + { + id: 'prefix', + header: 'Prefix' + }, + { + id: 'created', + header: 'Created' + }, + { + id: 'updated', + header: 'Updated' + }, + { + id: 'menu', + header: '' + } + ]} + serverSide={true} + onSort={() => {}} + > + {#snippet row(client, index)} + + {index + 1} + {client.id} + {client.access_token} + {client.prefix} + {getTimeDateString(client.created)} + {getTimeDateString(client.updated)} + + + {/snippet} + +
    From e881df452a7e126731db01fad990693547a03fa3 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Tue, 9 Sep 2025 08:41:53 -0500 Subject: [PATCH 011/144] Remove Operations Queue from frontend --- src/routes/+page.svelte | 120 ++++++++---------- src/routes/operation-queue-admin/+page.svelte | 2 - 2 files changed, 56 insertions(+), 66 deletions(-) delete mode 100644 src/routes/operation-queue-admin/+page.svelte diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 29775e1c..4ad54a9d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,72 +1,64 @@ + +

    App Publishing Service

    Administration

    -
    -
    -
    -
    -

    Job

    -

    - View, edit, or remove entries from the job table. Jobs point to the AWS S3 repository - that contains the source for the builds and publishes associated with this job. Deleting - job entries also deletes any associated builds and releases associated with the job. -

    -

    Job Administration »

    -
    -
    -

    Build

    -

    - View, edit, or remove entries from the build table. Each entry in this table contains a - url link to the instance of the associated AWS Codebuild build attempt. Deleting the - build also deletes any releases associated with this build. -

    -

    Build Administration »

    -
    -
    -

    Release

    -

    - View, edit, or remove entries from the release table. Entries in this table relate to - attempts to publish builds in Google Play store or other customized locations. Each - entry in this table contains a url link to the instance of the associated AWS Codebuild - publish attempt. -

    -

    Release Administration »

    -
    -
    -
    -
    -

    Client

    -

    - View, edit, or remove entries from the client table. Used if multiple Scriptoria sites - are sending requests to the build engine. Access tokens, which are used for the - Authentication: Bearer fields of requests are entered along with a prefix that is used - in naming jobs associated with this client. -

    -

    Client Administration »

    -
    -
    -

    Operation Queue

    -

    - View, edit, or remove entries from the operation queue table. Entries in this table - relate to internal operations that are either queued to be performed, waiting to be - retried, or have failed the maximum number of times and are present for reporting - purposes only. -

    -

    - Operation Queue Administration » -

    -
    -
    -

    Project

    -

    - View, edit, or remove entries from the project table. Each entry contains a link to be - used by Scripture App Builder to create or update a source repository in AWS S3. -

    -

    Project Administration »

    -
    -
    +
    +
    +

    Job

    +

    + View, edit, or remove entries from the job table. Jobs point to the AWS S3 repository that + contains the source for the builds and publishes associated with this job. Deleting job + entries also deletes any associated builds and releases associated with the job. +

    + Job Administration » +
    +
    +

    Build

    +

    + View, edit, or remove entries from the build table. Each entry in this table contains a url + link to the instance of the associated AWS Codebuild build attempt. Deleting the build also + deletes any releases associated with this build. +

    + Build Administration » +
    +
    +

    Release

    +

    + View, edit, or remove entries from the release table. Entries in this table relate to + attempts to publish builds in Google Play store or other customized locations. Each entry in + this table contains a url link to the instance of the associated AWS Codebuild publish + attempt. +

    + Release Administration » +
    +
    +

    Client

    +

    + View, edit, or remove entries from the client table. Used if multiple Scriptoria sites are + sending requests to the build engine. Access tokens, which are used for the Authentication: + Bearer fields of requests are entered along with a prefix that is used in naming jobs + associated with this client. +

    + Client Administration » +
    +
    +

    Project

    +

    + View, edit, or remove entries from the project table. Each entry contains a link to be used + by Scripture App Builder to create or update a source repository in AWS S3. +

    + Project Administration »
    + + diff --git a/src/routes/operation-queue-admin/+page.svelte b/src/routes/operation-queue-admin/+page.svelte deleted file mode 100644 index 154b3734..00000000 --- a/src/routes/operation-queue-admin/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ - -Under construction \ No newline at end of file From 9b77cc51aaa9b99cee7eea46a2a8212e876d396c Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Tue, 9 Sep 2025 08:43:11 -0500 Subject: [PATCH 012/144] Remove contact page --- src/routes/+layout.svelte | 1 - src/routes/contact/+page.svelte | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 src/routes/contact/+page.svelte diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index bedb961f..04a29550 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -24,7 +24,6 @@
    diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte deleted file mode 100644 index 154b3734..00000000 --- a/src/routes/contact/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ - -Under construction \ No newline at end of file From c81c7eb2e2166dbce2b5ba58433ab9001c103d1b Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Tue, 9 Sep 2025 08:49:20 -0500 Subject: [PATCH 013/144] Update site information and logo --- src/routes/+layout.svelte | 4 ++-- src/routes/about/+page.svelte | 13 ++++++++----- static/sil_logo_glyph.png | Bin 0 -> 8409 bytes 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 static/sil_logo_glyph.png diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 04a29550..688165af 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -18,7 +18,7 @@
    diff --git a/src/routes/queue-admin/[...rest]/+page.svelte b/src/routes/queue-admin/[...rest]/+page.svelte new file mode 100644 index 00000000..f1ee8bfc --- /dev/null +++ b/src/routes/queue-admin/[...rest]/+page.svelte @@ -0,0 +1 @@ + From 8da59c74000888b82b5dc51d4d82f7f3f2ca6e58 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 22 Oct 2025 09:39:53 -0500 Subject: [PATCH 072/144] Add stubs for BullMQ jobs --- src/lib/server/bullmq/BullWorker.ts | 28 +++++++++++++++++++++++++ src/lib/server/job-executors/build.ts | 10 +++++++++ src/lib/server/job-executors/index.ts | 5 +++++ src/lib/server/job-executors/polling.ts | 10 +++++++++ src/lib/server/job-executors/project.ts | 6 ++++++ src/lib/server/job-executors/publish.ts | 10 +++++++++ src/lib/server/job-executors/s3.ts | 10 +++++++++ 7 files changed, 79 insertions(+) create mode 100644 src/lib/server/job-executors/build.ts create mode 100644 src/lib/server/job-executors/index.ts create mode 100644 src/lib/server/job-executors/polling.ts create mode 100644 src/lib/server/job-executors/project.ts create mode 100644 src/lib/server/job-executors/publish.ts create mode 100644 src/lib/server/job-executors/s3.ts diff --git a/src/lib/server/bullmq/BullWorker.ts b/src/lib/server/bullmq/BullWorker.ts index e46e6437..4025565b 100644 --- a/src/lib/server/bullmq/BullWorker.ts +++ b/src/lib/server/bullmq/BullWorker.ts @@ -27,6 +27,12 @@ export class Builds extends BullWorker { super(BullMQ.QueueName.Builds); } async run(job: Job) { + switch (job.data.type) { + case BullMQ.JobType.Build_Product: + return Executor.Build.product(job as Job); + case BullMQ.JobType.Build_PostProcess: + return Executor.Build.postProcess(job as Job); + } } } @@ -35,6 +41,12 @@ export class S3 extends BullWorker { super(BullMQ.QueueName.S3); } async run(job: Job) { + switch (job.data.type) { + case BullMQ.JobType.S3_CopyArtifacts: + return Executor.S3.save(job as Job); + case BullMQ.JobType.S3_CopyError: + return Executor.S3.error(job as Job); + } } } @@ -43,6 +55,10 @@ export class Projects extends BullWorker { super(BullMQ.QueueName.Projects); } async run(job: Job) { + switch (job.data.type) { + case BullMQ.JobType.Project_Create: + return Executor.Project.create(job as Job); + } } } @@ -51,6 +67,12 @@ export class Publishing extends BullWorker { super(BullMQ.QueueName.Publishing); } async run(job: Job) { + switch (job.data.type) { + case BullMQ.JobType.Publish_Product: + return Executor.Publish.product(job as Job); + case BullMQ.JobType.Publish_PostProcess: + return Executor.Publish.postProcess(job as Job); + } } } @@ -59,5 +81,11 @@ export class Polling extends BullWorker { super(BullMQ.QueueName.Polling); } async run(job: Job) { + switch (job.data.type) { + case BullMQ.JobType.Poll_Build: + return Executor.Polling.build(job as Job); + case BullMQ.JobType.Poll_Publish: + return Executor.Polling.publish(job as Job); + } } } diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts new file mode 100644 index 00000000..a6ce5b9b --- /dev/null +++ b/src/lib/server/job-executors/build.ts @@ -0,0 +1,10 @@ +import type { Job } from 'bullmq'; +import type { BullMQ } from '../bullmq'; + +export async function product(job: Job): Promise { + return; +} + +export async function postProcess(job: Job): Promise { + return; +} diff --git a/src/lib/server/job-executors/index.ts b/src/lib/server/job-executors/index.ts new file mode 100644 index 00000000..9583389a --- /dev/null +++ b/src/lib/server/job-executors/index.ts @@ -0,0 +1,5 @@ +export * as Build from './build'; +export * as Polling from './polling'; +export * as Project from './project'; +export * as Publish from './publish'; +export * as S3 from './s3'; diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts new file mode 100644 index 00000000..e8c09673 --- /dev/null +++ b/src/lib/server/job-executors/polling.ts @@ -0,0 +1,10 @@ +import type { Job } from 'bullmq'; +import type { BullMQ } from '../bullmq'; + +export async function build(job: Job): Promise { + return; +} + +export async function publish(job: Job): Promise { + return; +} diff --git a/src/lib/server/job-executors/project.ts b/src/lib/server/job-executors/project.ts new file mode 100644 index 00000000..999a1af4 --- /dev/null +++ b/src/lib/server/job-executors/project.ts @@ -0,0 +1,6 @@ +import type { Job } from 'bullmq'; +import type { BullMQ } from '../bullmq'; + +export async function create(job: Job): Promise { + return; +} diff --git a/src/lib/server/job-executors/publish.ts b/src/lib/server/job-executors/publish.ts new file mode 100644 index 00000000..989e6bb2 --- /dev/null +++ b/src/lib/server/job-executors/publish.ts @@ -0,0 +1,10 @@ +import type { Job } from 'bullmq'; +import type { BullMQ } from '../bullmq'; + +export async function product(job: Job): Promise { + return; +} + +export async function postProcess(job: Job): Promise { + return; +} diff --git a/src/lib/server/job-executors/s3.ts b/src/lib/server/job-executors/s3.ts new file mode 100644 index 00000000..fff62c9e --- /dev/null +++ b/src/lib/server/job-executors/s3.ts @@ -0,0 +1,10 @@ +import type { Job } from 'bullmq'; +import type { BullMQ } from '../bullmq'; + +export async function save(job: Job): Promise { + return; +} + +export async function error(job: Job): Promise { + return; +} From 9cd418c18a6083138fdf33b4816f387ce1820e4a Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 22 Oct 2025 16:49:44 -0500 Subject: [PATCH 073/144] BullMQ jobs for save/error to S3 --- src/lib/server/actions/copy-to-s3.ts | 197 -------------------------- src/lib/server/actions/error-to-s3.ts | 51 ------- src/lib/server/artifacts-provider.ts | 43 ++++++ src/lib/server/aws/s3.ts | 27 ++-- src/lib/server/bullmq/types.ts | 4 + src/lib/server/job-executors/s3.ts | 174 ++++++++++++++++++++++- src/lib/server/models/build.ts | 54 +++++++ src/lib/server/models/release.ts | 19 ++- 8 files changed, 311 insertions(+), 258 deletions(-) delete mode 100644 src/lib/server/actions/copy-to-s3.ts delete mode 100644 src/lib/server/actions/error-to-s3.ts diff --git a/src/lib/server/actions/copy-to-s3.ts b/src/lib/server/actions/copy-to-s3.ts deleted file mode 100644 index e435f060..00000000 --- a/src/lib/server/actions/copy-to-s3.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { readFile } from 'node:fs/promises'; -import type { BuildForPrefix } from '../artifacts-provider'; -import { S3 } from '../aws/s3'; -import { Build } from '../models/build'; -import { Release } from '../models/release'; -import { prisma } from '../prisma'; -import { Utils } from '../utils'; - -export class CopyToS3Operation { - private build_id; - private maxRetries = 50; - private maxDelay = 30; - private alertAfter = 5; - private s3; - private parms; - - public constructor(id: number, parms: string) { - this.build_id = id; - this.parms = parms; - this.s3 = new S3(); - } - public async performOperation() { - console.log(`[${Utils.getPrefix()}] CopyToS3Operation ID: ${this.build_id}`); - if (this.parms === 'release') { - const release = await prisma.release.findUnique({ where: { id: this.build_id } }); - if (release) { - await this.s3.copyS3Folder(release); - await prisma.release.update({ - where: { id: this.build_id }, - data: { status: Release.Status.Completed } - }); - await this.s3.removeCodeBuildFolder(release); - } - } else { - const build = await prisma.build.findUnique({ where: { id: this.build_id } }); - if (build) { - const job = build.job; - if (job) { - await this.saveBuild(build); - await prisma.build.update({ - where: { id: this.build_id }, - data: { status: Build.Status.Completed, result: Build.Result.Success } - }); - await this.s3.removeCodeBuildFolder(build); - } - } - } - } - public getMaximumRetries() { - return this.maxRetries; - } - public getMaximumDelay() { - return this.maxDelay; - } - public getAlertAfterAttemptCount() { - return this.alertAfter; - } - - /** - * @param Build build - * @param string defaultLanguage - */ - private async getExtraContent(build: BuildForPrefix, defaultLanguage: string) { - console.log(`getExtraContent defaultLanguage: ${defaultLanguage}`); - const manifestFileContent = await this.s3.readS3File(build, 'manifest.txt'); - if (manifestFileContent) { - const manifestFiles = manifestFileContent.split('\n'); - if (manifestFiles.length > 0) { - // Copy index.html to destination folder - const path = './preview/playlisting/index.html'; - - const indexContents = (await readFile(path)).toString(); - this.s3.writeFileToS3(indexContents, 'play-listing/index.html', build); - } - if (!defaultLanguage) { - // If defaultLanguage was not found, use first entry with icon - for (const playListingFile of manifestFiles) { - const matches = playListingFile.match(/([^/]*)\/images\/icon.png/); - if (matches) { - defaultLanguage = matches[1]; - break; - } - } - } - - const playEncodedRelativePaths: string[] = []; - const languages = new Set(); - let publishIndex = '
      '; - const ignoreFiles = [ - 'default-language.txt', - 'primary-color.txt', - 'download-apk-strings.json' - ]; - for (const path of manifestFiles) { - if (ignoreFiles.includes(path)) { - continue; - } - if (path) { - // collect files - const encodedPath = CopyToS3Operation.encodePath('play-listing/' + path); - publishIndex += `
    • play-listing/${path}

    • `; - playEncodedRelativePaths.push(CopyToS3Operation.encodePath(path)); - - // collect languages - const langMatches = path.match(/([^/]*)\//); - if (langMatches) { - languages.add(langMatches[1]); - } - } - } - publishIndex += '
    '; - this.s3.writeFileToS3(publishIndex, 'play-listing.html', build); - const manifest: Record> = { - files: playEncodedRelativePaths, - languages: Array.from(languages), - color: await this.getPrimaryColor(build), - package: await this.getPackageName(build), - 'download-apk-strings': await this.getDownloadApkStrings(build, languages, defaultLanguage), - url: build.getArtifactUrlBase() + 'play-listing/' - }; - if (defaultLanguage) { - manifest['default-language'] = defaultLanguage; - manifest['icon'] = 'defaultLanguage/images/icon.png'; - } - const json = JSON.stringify(manifest); - const jsonFileName = 'play-listing/manifest.json'; - this.s3.writeFileToS3(json, jsonFileName, build); - } - } - - private static encodePath(path: string) { - return encodeURI(path); - } - - /** - * getDefaultLanguage reads the default language from default-language.txt - * - * @param Build build - Current build object - * @return string Contents of default-language.txt or empty if file doesn't exist - */ - private async getDefaultLanguage(build: BuildForPrefix) { - return await this.s3.readS3File(build, 'play-listing/default-language.txt'); - } - - /** - * getPrimaryColor read the primary color from primary-color.txt - * - * @param Build build - Current build object - * @return string Contents of primary-color.txt or default value is file doesn't exist - */ - private async getPrimaryColor(build: BuildForPrefix) { - return (await this.s3.readS3File(build, 'play-listing/primary-color.txt')).trim() || '#cce2ff'; - } - - /** - * getDownloadApkStrings read the localization strings in download-apk-strings.json - * - * @param Build build - Current build object - * @param array languages - languages to include - * @param string defaultLanguage - the default language - * @return mixed Contents of download-apk-strings.json or default as array - */ - private async getDownloadApkStrings( - build: BuildForPrefix, - languages: Set, - defaultLanguage: string - ) { - const languagesNoVariant = new Set(languages.values().map((lang) => lang.substring(0, 2))); - - const strings = - (await this.s3.readS3File(build, 'play-listing/download-apk-strings.json')).trim() || - JSON.stringify({ en: 'Download APK' }); - - const downloadApkStrings = Object.entries(JSON.parse(strings) as Record).filter( - ([lang]) => languagesNoVariant.has(lang) - ); - - return downloadApkStrings.length - ? Object.fromEntries(downloadApkStrings) - : { [defaultLanguage]: 'APK' }; - } - - private async getPackageName(build: BuildForPrefix) { - return (await this.s3.readS3File(build, 'package_name.txt')).trim(); - } - - /** - * Save the build to S3 + - * @param Build build - * @return string - */ - private async saveBuild(build: BuildForPrefix) { - this.s3.copyS3Folder(build); - const defaultLanguage = await this.getDefaultLanguage(build); - this.getExtraContent(build, defaultLanguage); - } -} diff --git a/src/lib/server/actions/error-to-s3.ts b/src/lib/server/actions/error-to-s3.ts deleted file mode 100644 index 33d7a63d..00000000 --- a/src/lib/server/actions/error-to-s3.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { S3 } from '../aws/s3'; -import { Build } from '../models/build'; -import { Release } from '../models/release'; -import { prisma } from '../prisma'; -import { Utils } from '../utils'; - -export class CopyErrorToS3Operation { - private id; - private parms; - private maxRetries = 50; - private maxDelay = 30; - private alertAfter = 5; - - public constructor(id: number, parms: string) { - this.id = id; - this.parms = parms; - } - public async performOperation() { - console.log(`[${Utils.getPrefix()}] CopyErrorToS3Operation ID: ${this.id}`); - if (this.parms === 'release') { - const release = await prisma.release.findUnique({ where: { id: this.id } }); - if (release) { - const s3 = new S3(); - await s3.copyS3Folder(release); - await prisma.release.update({ - where: { id: this.id }, - data: { status: Release.Status.Completed } - }); - } - } else { - const build = await prisma.build.findUnique({ where: { id: this.id } }); - if (build) { - const s3 = new S3(); - await s3.copyS3Folder(build); - await prisma.build.update({ - where: { id: this.id }, - data: { status: Build.Status.Completed } - }); - } - } - } - public getMaximumRetries() { - return this.maxRetries; - } - public getMaximumDelay() { - return this.maxDelay; - } - public getAlertAfterAttemptCount() { - return this.alertAfter; - } -} diff --git a/src/lib/server/artifacts-provider.ts b/src/lib/server/artifacts-provider.ts index 97619e15..90539735 100644 --- a/src/lib/server/artifacts-provider.ts +++ b/src/lib/server/artifacts-provider.ts @@ -1,5 +1,7 @@ import type { Prisma } from '@prisma/client'; import { AWSCommon } from './aws/common'; +import { Build } from './models/build'; +import { Release } from './models/release'; export type BuildForPrefix = Prisma.buildGetPayload<{ select: { id: true; job: { select: { id: true; app_id: true } } }; @@ -18,3 +20,44 @@ export function getBasePrefixUrl(artifacts_provider: ProviderForPrefix, productS : AWSCommon.getArtifactPath(artifacts_provider.job, productStage, true); return `${artifactPath}/${artifacts_provider.id}`; } + +export type BuildForArtifacts = Prisma.buildGetPayload<{ + select: { id: true; artifact_url_base: true; artifact_files: true; version_code: true }; +}>; + +export type ReleaseForArtifacts = Prisma.releaseGetPayload<{ + select: { id: true; artifact_url_base: true; artifact_files: true }; +}>; + +export type ProviderForArtifacts = BuildForArtifacts | ReleaseForArtifacts; + +export function beginArtifacts(artifacts_provider: ProviderForArtifacts, baseUrl: string) { + artifacts_provider.artifact_url_base = baseUrl; + artifacts_provider.artifact_files = null; +} + +export function handleArtifact(provider: ProviderForArtifacts, key: string, contents: string = '') { + if ('version_code' in provider) { + const [type, name] = Build.artifactType(key); + if (type !== Build.Artifact.Unknown) { + if (provider.artifact_files) { + provider.artifact_files += ',' + name; + } else { + provider.artifact_files = name; + } + + if (type === Build.Artifact.VersionCode) { + provider.version_code = Number(contents); + } + } + } else { + const [type, name] = Release.artifactType(key); + if (type !== Release.Artifact.Unknown) { + if (provider.artifact_files) { + provider.artifact_files += ',' + name; + } else { + provider.artifact_files = name; + } + } + } +} diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index c1dd4987..32e4555a 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -14,8 +14,11 @@ import { AWSCommon } from './common'; import { env } from '$env/dynamic/private'; import { type BuildForPrefix, + type ProviderForArtifacts, type ProviderForPrefix, - getBasePrefixUrl + beginArtifacts, + getBasePrefixUrl, + handleArtifact } from '$lib/server/artifacts-provider'; import { Utils } from '$lib/server/utils'; @@ -101,12 +104,12 @@ export class S3 extends AWSCommon { * * @param Build or Release artifacts_provider - The build or release */ - public async copyS3Folder(artifacts_provider: ProviderForPrefix) { + public async copyS3Folder(artifacts_provider: ProviderForPrefix & ProviderForArtifacts) { const artifactsBucket = S3.getArtifactsBucket(); const sourcePrefix = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; const destPrefix = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()) + '/'; - const publicBaseUrl = this.s3Client.getObjectUrl(artifactsBucket, destPrefix); - artifacts_provider.beginArtifacts(publicBaseUrl); + const publicBaseUrl = `https://${artifactsBucket}.s3.amazonaws.com/${destPrefix}`; + beginArtifacts(artifacts_provider, publicBaseUrl); const destFolderPrefix = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()); try { await this.deleteMatchingObjects(artifactsBucket, destFolderPrefix); @@ -142,7 +145,7 @@ export class S3 extends AWSCommon { file: _Object, sourcePrefix: string, destPrefix: string, - artifacts_provider: ProviderForPrefix + artifacts_provider: ProviderForPrefix & ProviderForArtifacts ) { const artifactsBucket = S3.getArtifactsBucket(); let fileContents = ''; @@ -176,7 +179,11 @@ export class S3 extends AWSCommon { MetadataDirective: 'REPLACE' }) ); - artifacts_provider.handleArtifact(destinationFile, fileContents); + if ('build' in artifacts_provider) { + handleArtifact(artifacts_provider, destinationFile, fileContents); + } else { + handleArtifact(artifacts_provider, destinationFile); + } return ret; } catch (e) { if (e instanceof Error) { @@ -190,7 +197,7 @@ export class S3 extends AWSCommon { public async writeFileToS3( fileContents: string, fileName: string, - artifacts_provider: ProviderForPrefix + artifacts_provider: ProviderForPrefix & ProviderForArtifacts ) { const fileS3Bucket = S3.getArtifactsBucket(); const destPrefix: string = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()); @@ -206,7 +213,11 @@ export class S3 extends AWSCommon { }) ); - artifacts_provider.handleArtifact(fileS3Key, fileContents); + if ('build' in artifacts_provider) { + handleArtifact(artifacts_provider, fileS3Key, fileContents); + } else { + handleArtifact(artifacts_provider, fileS3Key); + } } public async removeCodeBuildFolder(artifacts_provider: ProviderForPrefix) { const s3Folder = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; diff --git a/src/lib/server/bullmq/types.ts b/src/lib/server/bullmq/types.ts index d9e30be3..73791451 100644 --- a/src/lib/server/bullmq/types.ts +++ b/src/lib/server/bullmq/types.ts @@ -104,9 +104,13 @@ export namespace Publish { export namespace S3 { export interface CopyArtifacts { type: JobType.S3_CopyArtifacts; + scope: 'build' | 'release'; + id: number; } export interface CopyErrors { type: JobType.S3_CopyError; + scope: 'build' | 'release'; + id: number; } } diff --git a/src/lib/server/job-executors/s3.ts b/src/lib/server/job-executors/s3.ts index fff62c9e..18560257 100644 --- a/src/lib/server/job-executors/s3.ts +++ b/src/lib/server/job-executors/s3.ts @@ -1,10 +1,182 @@ import type { Job } from 'bullmq'; +import { readFile } from 'node:fs/promises'; +import type { BuildForPrefix } from '../artifacts-provider'; +import { S3 } from '../aws/s3'; import type { BullMQ } from '../bullmq'; +import { Build } from '../models/build'; +import { Release } from '../models/release'; +import { prisma } from '../prisma'; export async function save(job: Job): Promise { - return; + const id = job.data.id; + const s3 = new S3(); + if (job.data.scope === 'release') { + const release = await prisma.release.findUnique({ + where: { id }, + include: { build: { include: { job: true } } } + }); + if (release) { + await s3.copyS3Folder(release); + await prisma.release.update({ + where: { id }, + data: { ...release, status: Release.Status.Completed, build: undefined } + }); + await s3.removeCodeBuildFolder(release); + } + } else { + const build = await prisma.build.findUnique({ + where: { id }, + include: { job: true } + }); + if (build) { + if (build.job) { + s3.copyS3Folder(build); + let defaultLanguage = await s3.readS3File(build, 'play-listing/default-language.txt'); + console.log(`getExtraContent defaultLanguage: ${defaultLanguage}`); + const manifestFileContent = await s3.readS3File(build, 'manifest.txt'); + let manifest: Record> = {}; + if (manifestFileContent) { + const manifestFiles = manifestFileContent.split('\n'); + if (manifestFiles.length > 0) { + // Copy index.html to destination folder + const path = './preview/playlisting/index.html'; + + const indexContents = (await readFile(path)).toString(); + s3.writeFileToS3(indexContents, 'play-listing/index.html', build); + } + if (!defaultLanguage) { + // If defaultLanguage was not found, use first entry with icon + for (const playListingFile of manifestFiles) { + const matches = playListingFile.match(/([^/]*)\/images\/icon.png/); + if (matches) { + defaultLanguage = matches[1]; + break; + } + } + } + + const playEncodedRelativePaths: string[] = []; + const languages = new Set(); + let publishIndex = '
      '; + const ignoreFiles = [ + 'default-language.txt', + 'primary-color.txt', + 'download-apk-strings.json' + ]; + for (const path of manifestFiles) { + if (ignoreFiles.includes(path)) { + continue; + } + if (path) { + // collect files + const encodedPath = encodeURI('play-listing/' + path); + publishIndex += `
    • play-listing/${path}

    • `; + playEncodedRelativePaths.push(encodeURI(path)); + + // collect languages + const langMatches = path.match(/([^/]*)\//); + if (langMatches) { + languages.add(langMatches[1]); + } + } + } + publishIndex += '
    '; + s3.writeFileToS3(publishIndex, 'play-listing.html', build); + manifest = { + files: playEncodedRelativePaths, + languages: Array.from(languages), + color: + (await s3.readS3File(build, 'play-listing/primary-color.txt')).trim() || '#cce2ff', + package: (await s3.readS3File(build, 'package_name.txt')).trim(), + 'download-apk-strings': await getDownloadApkStrings( + s3, + build, + languages, + defaultLanguage + ), + url: build.artifact_url_base + 'play-listing/' + }; + if (defaultLanguage) { + manifest['default-language'] = defaultLanguage; + manifest['icon'] = 'defaultLanguage/images/icon.png'; + } + const json = JSON.stringify(manifest); + const jsonFileName = 'play-listing/manifest.json'; + s3.writeFileToS3(json, jsonFileName, build); + } + await prisma.build.update({ + where: { id }, + data: { + ...build, + status: Build.Status.Completed, + result: Build.Result.Success, + job: undefined + } + }); + await s3.removeCodeBuildFolder(build); + job.updateProgress(100); + return { manifest }; + } + } + } + job.updateProgress(100); } export async function error(job: Job): Promise { + const id = job.data.id; + const s3 = new S3(); + if (job.data.scope === 'release') { + const release = await prisma.release.findUnique({ + where: { id }, + include: { build: { include: { job: true } } } + }); + if (release) { + await s3.copyS3Folder(release); + await prisma.release.update({ + where: { id }, + data: { status: Release.Status.Completed } + }); + } + } else { + const build = await prisma.build.findUnique({ where: { id }, include: { job: true } }); + if (build) { + await s3.copyS3Folder(build); + await prisma.build.update({ + where: { id }, + data: { status: Build.Status.Completed } + }); + } + } + job.updateProgress(100); return; } + +/** + * getDownloadApkStrings read the localization strings in download-apk-strings.json + * + * @param S3 s3 - S3 client + * @param Build build - Current build object + * @param array languages - languages to include + * @param string defaultLanguage - the default language + * @return mixed Contents of download-apk-strings.json or default as array + */ +async function getDownloadApkStrings( + s3: S3, + build: BuildForPrefix, + languages: Set, + defaultLanguage: string +) { + const languagesNoVariant = new Set(languages.values().map((lang) => lang.substring(0, 2))); + + const strings = + (await s3.readS3File(build, 'play-listing/download-apk-strings.json')).trim() || + JSON.stringify({ en: 'Download APK' }); + + const downloadApkStrings = Object.entries(JSON.parse(strings) as Record).filter( + ([lang]) => languagesNoVariant.has(lang) + ); + + return downloadApkStrings.length + ? Object.fromEntries(downloadApkStrings) + : { [defaultLanguage]: 'APK' }; +} diff --git a/src/lib/server/models/build.ts b/src/lib/server/models/build.ts index 45269533..4701564f 100644 --- a/src/lib/server/models/build.ts +++ b/src/lib/server/models/build.ts @@ -1,3 +1,5 @@ +import { basename, extname } from 'node:path'; + // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Build { export enum Status { @@ -44,4 +46,56 @@ export namespace Build { AssetNotify = 'asset-notify', DataSafetyCsv = 'data-safety-csv' } + + export function artifactType(key: string): [Artifact, string] { + const ext = extname(key); + let file = basename(key); + let type = Artifact.Unknown; + if (file === 'cloudWatch') { + type = Artifact.CloudWatch; + } else if (ext === 'log') { + type = Artifact.ConsoleText; + } else if (ext === 'aab') { + type = Artifact.AAB; + } else if (ext === 'apk') { + type = Artifact.APK; + } else if (file === 'version_code.txt') { + type = Artifact.VersionCode; + } else if (file === 'version.json') { + type = Artifact.Version; + } else if (file === 'package_name.txt') { + type = Artifact.PackageName; + } else if (file === 'publish-properties.json') { + type = Artifact.PublishProperties; + } else if (file === 'about.txt') { + type = Artifact.About; + } else if (file === 'whats_new.txt') { + type = Artifact.WhatsNew; + } else if (file === 'html.zip') { + type = Artifact.HTML; + } else if (file === 'pwa.zip') { + type = Artifact.PWA; + } else if (file === 'private_key.pepk') { + type = Artifact.EncryptedKey; + } else if (key.match(/asset-package\/.*\.zip/)) { + type = Artifact.AssetPackage; + file = 'asset-package/' + file; + } else if (key.match(/asset-package\/preview\.html/)) { + type = Artifact.AssetPreview; + file = 'asset-package/preview.html'; + } else if (key.match(/asset-package\/notify\.json/)) { + type = Artifact.AssetNotify; + file = 'asset-package/notify.json'; + } else if (key.match(/play-listing\/index\.html/)) { + type = Artifact.PlayListing; + file = 'play-listing/index.html'; + } else if (key.match(/play-listing\/manifest.json/)) { + type = Artifact.PlayListingManifest; + file = 'play-listing/manifest.json'; + } else if (key.match(/data_safety\.csv/)) { + type = Artifact.DataSafetyCsv; + } + + return [type, file]; + } } diff --git a/src/lib/server/models/release.ts b/src/lib/server/models/release.ts index df256121..f056d12a 100644 --- a/src/lib/server/models/release.ts +++ b/src/lib/server/models/release.ts @@ -1,3 +1,5 @@ +import { basename, extname } from 'node:path'; + // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Release { export enum Status { @@ -12,6 +14,21 @@ export namespace Release { export enum Artifact { CloudWatch = 'cloudWatch', ConsoleText = 'consoleText', - PublishUrl = 'publishUrl' + PublishUrl = 'publishUrl', + Unknown = 'unknown' + } + + export function artifactType(key: string): [Artifact, string] { + const ext = extname(key); + const file = basename(key); + let type = Artifact.Unknown; + if (file === 'cloudWatch') { + type = Artifact.CloudWatch; + } else if (ext === 'log') { + type = Artifact.ConsoleText; + } else if (file === 'publish_url.txt') { + type = Artifact.PublishUrl; + } + return [type, file]; } } From c682d3a74f17bc9295ab42f71066db0dfe67deac Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 23 Oct 2025 11:28:48 -0500 Subject: [PATCH 074/144] Relocate models and artifacts --- src/lib/{utils => models}/artifacts.ts | 69 +++++++++++++++++++ src/lib/{server => }/models/build.ts | 0 src/lib/{server => }/models/job.ts | 0 src/lib/{server => }/models/release.ts | 0 src/lib/server/actions/build.ts | 2 +- src/lib/server/actions/release.ts | 4 +- src/lib/server/artifacts-provider.ts | 63 ----------------- src/lib/server/aws/codebuild.ts | 8 +-- src/lib/server/aws/common.ts | 7 -- src/lib/server/aws/s3.ts | 2 +- src/lib/server/job-executors/s3.ts | 12 ++-- .../job/[jobId=idNumber]/build/+server.ts | 2 +- .../build/[buildId=idNumber]/+server.ts | 2 +- .../release/[releaseId=idNumber]/+server.ts | 2 +- src/routes/build-admin/view/+page.svelte | 2 +- src/routes/release-admin/view/+page.svelte | 2 +- 16 files changed, 88 insertions(+), 89 deletions(-) rename src/lib/{utils => models}/artifacts.ts (69%) rename src/lib/{server => }/models/build.ts (100%) rename src/lib/{server => }/models/job.ts (100%) rename src/lib/{server => }/models/release.ts (100%) delete mode 100644 src/lib/server/artifacts-provider.ts diff --git a/src/lib/utils/artifacts.ts b/src/lib/models/artifacts.ts similarity index 69% rename from src/lib/utils/artifacts.ts rename to src/lib/models/artifacts.ts index 016abc9b..9cd864ad 100644 --- a/src/lib/utils/artifacts.ts +++ b/src/lib/models/artifacts.ts @@ -1,4 +1,6 @@ import type { Prisma } from '@prisma/client'; +import { Build } from './build'; +import { Release } from './release'; const ARTIFACT_AAB = 'aab'; const ARTIFACT_APK = 'apk'; @@ -142,3 +144,70 @@ function getArtifactUrls( .map((f) => (artifact_url_base ?? '') + encodeFilename(f)); return urls?.length ? urls : null; } + +export type BuildForPrefix = Prisma.buildGetPayload<{ + select: { id: true; job: { select: { id: true; app_id: true } } }; +}>; + +export type ReleaseForPrefix = Prisma.releaseGetPayload<{ + select: { id: true; build: { select: { job: { select: { id: true; app_id: true } } } } }; +}>; + +export type ProviderForPrefix = BuildForPrefix | ReleaseForPrefix; + +export function getArtifactPath( + job: Prisma.jobGetPayload<{ select: { app_id: true; id: true } }>, + productionStage: string, + isPublish = false +) { + return `${productionStage}/jobs/${isPublish ? 'publish' : 'build'}_${job.app_id}_${job.id}`; +} + +export function getBasePrefixUrl(artifacts_provider: ProviderForPrefix, productStage: string) { + const artifactPath = + 'build' in artifacts_provider + ? getArtifactPath(artifacts_provider.build.job, productStage, true) + : getArtifactPath(artifacts_provider.job, productStage, true); + return `${artifactPath}/${artifacts_provider.id}`; +} + +export type BuildForArtifacts = Prisma.buildGetPayload<{ + select: { id: true; artifact_url_base: true; artifact_files: true; version_code: true }; +}>; + +export type ReleaseForArtifacts = Prisma.releaseGetPayload<{ + select: { id: true; artifact_url_base: true; artifact_files: true }; +}>; + +export type ProviderForArtifacts = BuildForArtifacts | ReleaseForArtifacts; + +export function beginArtifacts(artifacts_provider: ProviderForArtifacts, baseUrl: string) { + artifacts_provider.artifact_url_base = baseUrl; + artifacts_provider.artifact_files = null; +} + +export function handleArtifact(provider: ProviderForArtifacts, key: string, contents: string = '') { + if ('version_code' in provider) { + const [type, name] = Build.artifactType(key); + if (type !== Build.Artifact.Unknown) { + if (provider.artifact_files) { + provider.artifact_files += ',' + name; + } else { + provider.artifact_files = name; + } + + if (type === Build.Artifact.VersionCode) { + provider.version_code = Number(contents); + } + } + } else { + const [type, name] = Release.artifactType(key); + if (type !== Release.Artifact.Unknown) { + if (provider.artifact_files) { + provider.artifact_files += ',' + name; + } else { + provider.artifact_files = name; + } + } + } +} diff --git a/src/lib/server/models/build.ts b/src/lib/models/build.ts similarity index 100% rename from src/lib/server/models/build.ts rename to src/lib/models/build.ts diff --git a/src/lib/server/models/job.ts b/src/lib/models/job.ts similarity index 100% rename from src/lib/server/models/job.ts rename to src/lib/models/job.ts diff --git a/src/lib/server/models/release.ts b/src/lib/models/release.ts similarity index 100% rename from src/lib/server/models/release.ts rename to src/lib/models/release.ts diff --git a/src/lib/server/actions/build.ts b/src/lib/server/actions/build.ts index 7e50e80e..c4f12d2c 100644 --- a/src/lib/server/actions/build.ts +++ b/src/lib/server/actions/build.ts @@ -2,7 +2,7 @@ import type { Prisma } from '@prisma/client'; import { readFile } from 'node:fs/promises'; import { type BuildForCodeBuild, CodeBuild } from '../aws/codebuild'; import { CodeCommit } from '../aws/codecommit'; -import { Build } from '../models/build'; +import { Build } from '../../models/build'; import { prisma } from '../prisma'; import { Utils } from '../utils'; diff --git a/src/lib/server/actions/release.ts b/src/lib/server/actions/release.ts index 7a6008c8..da21fc1d 100644 --- a/src/lib/server/actions/release.ts +++ b/src/lib/server/actions/release.ts @@ -1,8 +1,8 @@ import type { Prisma } from '@prisma/client'; import { readFile } from 'node:fs/promises'; import { CodeBuild, type ReleaseForCodeBuild } from '../aws/codebuild'; -import { Build } from '../models/build'; -import { Release } from '../models/release'; +import { Build } from '../../models/build'; +import { Release } from '../../models/release'; import { prisma } from '../prisma'; import { Utils } from '../utils'; diff --git a/src/lib/server/artifacts-provider.ts b/src/lib/server/artifacts-provider.ts deleted file mode 100644 index 90539735..00000000 --- a/src/lib/server/artifacts-provider.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Prisma } from '@prisma/client'; -import { AWSCommon } from './aws/common'; -import { Build } from './models/build'; -import { Release } from './models/release'; - -export type BuildForPrefix = Prisma.buildGetPayload<{ - select: { id: true; job: { select: { id: true; app_id: true } } }; -}>; - -export type ReleaseForPrefix = Prisma.releaseGetPayload<{ - select: { id: true; build: { select: { job: { select: { id: true; app_id: true } } } } }; -}>; - -export type ProviderForPrefix = BuildForPrefix | ReleaseForPrefix; - -export function getBasePrefixUrl(artifacts_provider: ProviderForPrefix, productStage: string) { - const artifactPath = - 'build' in artifacts_provider - ? AWSCommon.getArtifactPath(artifacts_provider.build.job, productStage, true) - : AWSCommon.getArtifactPath(artifacts_provider.job, productStage, true); - return `${artifactPath}/${artifacts_provider.id}`; -} - -export type BuildForArtifacts = Prisma.buildGetPayload<{ - select: { id: true; artifact_url_base: true; artifact_files: true; version_code: true }; -}>; - -export type ReleaseForArtifacts = Prisma.releaseGetPayload<{ - select: { id: true; artifact_url_base: true; artifact_files: true }; -}>; - -export type ProviderForArtifacts = BuildForArtifacts | ReleaseForArtifacts; - -export function beginArtifacts(artifacts_provider: ProviderForArtifacts, baseUrl: string) { - artifacts_provider.artifact_url_base = baseUrl; - artifacts_provider.artifact_files = null; -} - -export function handleArtifact(provider: ProviderForArtifacts, key: string, contents: string = '') { - if ('version_code' in provider) { - const [type, name] = Build.artifactType(key); - if (type !== Build.Artifact.Unknown) { - if (provider.artifact_files) { - provider.artifact_files += ',' + name; - } else { - provider.artifact_files = name; - } - - if (type === Build.Artifact.VersionCode) { - provider.version_code = Number(contents); - } - } - } else { - const [type, name] = Release.artifactType(key); - if (type !== Release.Artifact.Unknown) { - if (provider.artifact_files) { - provider.artifact_files += ',' + name; - } else { - provider.artifact_files = name; - } - } - } -} diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index b222abab..878e9024 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -11,8 +11,8 @@ import { import type { Prisma } from '@prisma/client'; import { AWSCommon } from './common'; import { S3 } from './s3'; -import { type BuildForPrefix, getBasePrefixUrl } from '$lib/server/artifacts-provider'; -import { Job } from '$lib/server/models/job'; +import { type BuildForPrefix, getArtifactPath, getBasePrefixUrl } from '$lib/models/artifacts'; +import { Job } from '$lib/models/job'; import { Utils } from '$lib/server/utils'; export type ReleaseForCodeBuild = Prisma.releaseGetPayload<{ @@ -84,7 +84,7 @@ export class CodeBuild extends AWSCommon { const secretsBucket = CodeBuild.getSecretsBucket(); const buildApp = CodeBuild.getCodeBuildProjectName('build_app'); const buildPath = this.getBuildPath(job); - const artifactPath = CodeBuild.getArtifactPath(job, 'codebuild-output'); + const artifactPath = getArtifactPath(job, 'codebuild-output'); console.log(`Artifacts path: ${artifactPath}`); // Leaving all this code together to make it easier to remove when git is no longer supported if (codeCommit) { @@ -282,7 +282,7 @@ export class CodeBuild extends AWSCommon { const job = build.job; const buildPath = this.getBuildPath(job); const artifacts_bucket = CodeBuild.getArtifactsBucket(); - const artifactPath = CodeBuild.getArtifactPath(job, 'codebuild-output', true); + const artifactPath = getArtifactPath(job, 'codebuild-output', true); const secretsBucket = CodeBuild.getSecretsBucket(); const scriptureEarthKey = CodeBuild.getScriptureEarthKey(); const publishApp = CodeBuild.getCodeBuildProjectName('publish_app'); diff --git a/src/lib/server/aws/common.ts b/src/lib/server/aws/common.ts index ffb4fc38..342ba903 100644 --- a/src/lib/server/aws/common.ts +++ b/src/lib/server/aws/common.ts @@ -43,13 +43,6 @@ export class AWSCommon { public static getBuildScriptPath() { return `s3://${AWSCommon.getProjectsBucket()}/default`; } - public static getArtifactPath( - job: Prisma.jobGetPayload<{ select: { app_id: true; id: true } }>, - productionStage: string, - isPublish = false - ) { - return `${productionStage}/jobs/${isPublish ? 'publish' : 'build'}_${job.app_id}_${job.id}`; - } /** * Get the project name which is the prd or stg plus build_app or publish_app * diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index 32e4555a..e2ec192c 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -19,7 +19,7 @@ import { beginArtifacts, getBasePrefixUrl, handleArtifact -} from '$lib/server/artifacts-provider'; +} from '$lib/models/artifacts'; import { Utils } from '$lib/server/utils'; export class S3 extends AWSCommon { diff --git a/src/lib/server/job-executors/s3.ts b/src/lib/server/job-executors/s3.ts index 18560257..b2e1e5c8 100644 --- a/src/lib/server/job-executors/s3.ts +++ b/src/lib/server/job-executors/s3.ts @@ -1,11 +1,11 @@ import type { Job } from 'bullmq'; import { readFile } from 'node:fs/promises'; -import type { BuildForPrefix } from '../artifacts-provider'; -import { S3 } from '../aws/s3'; -import type { BullMQ } from '../bullmq'; -import { Build } from '../models/build'; -import { Release } from '../models/release'; -import { prisma } from '../prisma'; +import type { BuildForPrefix } from '$lib/models/artifacts'; +import { Build } from '$lib/models/build'; +import { Release } from '$lib/models/release'; +import { S3 } from '$lib/server/aws/s3'; +import type { BullMQ } from '$lib/server/bullmq'; +import { prisma } from '$lib/server/prisma'; export async function save(job: Job): Promise { const id = job.data.id; diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts index 52a94f71..bcfad890 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; -import { artifacts } from '$lib/utils/artifacts'; +import { artifacts } from '$lib/models/artifacts'; const buildSchema = v.strictObject({ targets: v.string(), diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts index fc45becb..77b1e2e8 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; -import { artifacts, releaseArtifacts } from '$lib/utils/artifacts'; +import { artifacts, releaseArtifacts } from '$lib/models/artifacts'; // GET /job/[id]/build/[id] export const GET: RequestHandler = async ({ params }) => { diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts index 07590b0b..9a2d8649 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts @@ -1,7 +1,7 @@ import type { RequestHandler } from './$types'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; -import { releaseArtifacts } from '$lib/utils/artifacts'; +import { releaseArtifacts } from '$lib/models/artifacts'; // GET /job/[id]/build/[id]/release/[id] export const GET: RequestHandler = async ({ params }) => { diff --git a/src/routes/build-admin/view/+page.svelte b/src/routes/build-admin/view/+page.svelte index 8a7e12dc..b7d8642d 100644 --- a/src/routes/build-admin/view/+page.svelte +++ b/src/routes/build-admin/view/+page.svelte @@ -3,7 +3,7 @@ import { page } from '$app/state'; import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import { title } from '$lib/stores'; - import { artifacts } from '$lib/utils/artifacts'; + import { artifacts } from '$lib/models/artifacts'; import { getTimeDateString } from '$lib/utils/time'; $title = 'View Build: ' + page.url.searchParams.get('id')!; diff --git a/src/routes/release-admin/view/+page.svelte b/src/routes/release-admin/view/+page.svelte index 099db15d..5be8f2ef 100644 --- a/src/routes/release-admin/view/+page.svelte +++ b/src/routes/release-admin/view/+page.svelte @@ -3,7 +3,7 @@ import { page } from '$app/state'; import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import { title } from '$lib/stores'; - import { getArtifactUrl } from '$lib/utils/artifacts'; + import { getArtifactUrl } from '$lib/models/artifacts'; import { getTimeDateString } from '$lib/utils/time'; $title = 'View Release: ' + page.url.searchParams.get('id')!; From 6939a49be5a61a862d6aaa8c62a6d412b5eb4ed5 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 23 Oct 2025 11:34:49 -0500 Subject: [PATCH 075/144] Fix missing function in CodeBuild --- src/lib/models/artifacts.ts | 2 +- src/lib/server/aws/codebuild.ts | 12 +++++++++--- src/lib/server/aws/s3.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lib/models/artifacts.ts b/src/lib/models/artifacts.ts index 9cd864ad..58949e1f 100644 --- a/src/lib/models/artifacts.ts +++ b/src/lib/models/artifacts.ts @@ -127,7 +127,7 @@ export function getArtifactUrl( function encodeFilename(filename: string) { return encodeURI(filename); } -function getArtifactFilename(pattern: RegExp, artifact_files: string | null) { +export function getArtifactFilename(pattern: RegExp, artifact_files: string | null) { return artifact_files?.split(',').find((f) => f.match(pattern)) ?? null; } function getArtfactFilenameCount(pattern: RegExp, artifact_files: string | null) { diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index 878e9024..b1bbf6b2 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -11,7 +11,12 @@ import { import type { Prisma } from '@prisma/client'; import { AWSCommon } from './common'; import { S3 } from './s3'; -import { type BuildForPrefix, getArtifactPath, getBasePrefixUrl } from '$lib/models/artifacts'; +import { + type BuildForPrefix, + getArtifactFilename, + getArtifactPath, + getBasePrefixUrl +} from '$lib/models/artifacts'; import { Job } from '$lib/models/job'; import { Utils } from '$lib/server/utils'; @@ -25,6 +30,7 @@ export type ReleaseForCodeBuild = Prisma.releaseGetPayload<{ build: { select: { id: true; + artifact_files: true; job: { select: { id: true; app_id: true; publisher_id: true; git_url: true } }; }; }; @@ -379,11 +385,11 @@ export class CodeBuild extends AWSCommon { */ private getSourceLocation( build: Prisma.buildGetPayload<{ - select: { id: true; job: { select: { id: true; app_id: true } } }; + select: { id: true; artifact_files: true; job: { select: { id: true; app_id: true } } }; }> ) { const appEnv = S3.getAppEnv(); - const apkFilename = build.apkFilename(); + const apkFilename = getArtifactFilename(/\.apk$/, build.artifact_files); const sourceLocation = S3.getS3Arn(build, appEnv, apkFilename); return sourceLocation; } diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index e2ec192c..1668c943 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -56,7 +56,7 @@ export class S3 extends AWSCommon { * @param string filename - Name of s3 file that arn is requested for * @return string prefix */ - public static getS3Arn(build: BuildForPrefix, productStage: string, filename: string) { + public static getS3Arn(build: BuildForPrefix, productStage: string, filename: string | null) { return `arn:aws:s3:::${S3.getArtifactsBucket()}/${getBasePrefixUrl(build, productStage)}/${filename ?? ''}`; } /** From 204509f83de1b10b07b0b8af3c16d704c19da577 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 23 Oct 2025 11:57:00 -0500 Subject: [PATCH 076/144] Move artifacts functions to individual models --- src/lib/models/artifacts.ts | 122 +----------------- src/lib/models/build.ts | 78 +++++++++++ src/lib/models/release.ts | 19 +++ .../job/[jobId=idNumber]/build/+server.ts | 4 +- .../build/[buildId=idNumber]/+server.ts | 7 +- .../release/[releaseId=idNumber]/+server.ts | 4 +- src/routes/build-admin/view/+page.svelte | 4 +- src/routes/release-admin/view/+page.svelte | 33 ++--- 8 files changed, 121 insertions(+), 150 deletions(-) diff --git a/src/lib/models/artifacts.ts b/src/lib/models/artifacts.ts index 58949e1f..502b1711 100644 --- a/src/lib/models/artifacts.ts +++ b/src/lib/models/artifacts.ts @@ -2,117 +2,6 @@ import type { Prisma } from '@prisma/client'; import { Build } from './build'; import { Release } from './release'; -const ARTIFACT_AAB = 'aab'; -const ARTIFACT_APK = 'apk'; -const ARTIFACT_VERSION_CODE = 'version_code'; -const ARTIFACT_VERSION = 'version'; -const ARTIFACT_ABOUT = 'about'; -const ARTIFACT_PLAY_LISTING = 'play-listing'; -const ARTIFACT_PLAY_LISTING_MANIFEST = 'play-listing-manifest'; -const ARTIFACT_PACKAGE_NAME = 'package_name'; -const ARTIFACT_PUBLISH_PROPERTIES = 'publish_properties'; -const ARTIFACT_CLOUD_WATCH = 'cloudWatch'; -const ARTIFACT_CONSOLE_TEXT = 'consoleText'; -const ARTIFACT_WHATS_NEW = 'whats_new'; -const ARTIFACT_HTML = 'html'; -const ARTIFACT_PWA = 'pwa'; -const ARTIFACT_ENCRYPTED_KEY = 'encrypted_key'; -const ARTIFACT_ASSET_PACKAGE = 'asset-package'; -const ARTIFACT_ASSET_PREVIEW = 'asset-preview'; -const ARTIFACT_ASSET_NOTIFY = 'asset-notify'; -const ARTIFACT_DATA_SAFETY_CSV = 'data-safety-csv'; - -const ARTIFACT_PUBLISH_URL = 'publishUrl'; - -export function artifacts( - build: Prisma.buildGetPayload<{ - select: { - targets: true; - artifact_url_base: true; - console_text_url: true; - artifact_files: true; - }; - }> -) { - const { targets, artifact_url_base: base, artifact_files: files } = build; - // We need to at least have one artifact or the current Portal will fail to parse the JSON. - // TODO: Treat these like the others once Portal is fixed. - const artifacts = { - [ARTIFACT_VERSION]: getArtifactUrl(/version\.json/, base, files), - [ARTIFACT_CLOUD_WATCH]: build.console_text_url, - // We used to return consoleText as the first item that matched "\.log" and we REALLY want console.log - // which is generated by BuildEngine. There is a APK_NAME-output.log which doesn't have everything in the log. - [ARTIFACT_CONSOLE_TEXT]: getArtifactUrl(/console\.log/, base, files), - [ARTIFACT_PUBLISH_PROPERTIES]: getArtifactUrl(/publish-properties\.json/, base, files) - } as Record; - if (targets?.match('apk')) { - artifacts[ARTIFACT_AAB] = getArtifactUrl(/\.aab/, base, files); - const count = getArtfactFilenameCount(/\.apk/, files); - if (count > 1) { - for (const apk of getArtifactUrls(/\.apk/, base, files) ?? []) { - const matches = apk.match(/-([^.]+)\.apk/); - artifacts[matches?.at(1) + '-' + ARTIFACT_APK] = apk; - } - } else { - artifacts[ARTIFACT_APK] = getArtifactUrl(/\.apk/, base, files); - } - artifacts[ARTIFACT_ENCRYPTED_KEY] = getArtifactUrl(/_key\.pepk/, base, files); - artifacts[ARTIFACT_ABOUT] = getArtifactUrl(/about\.txt/, base, files); - artifacts[ARTIFACT_DATA_SAFETY_CSV] = getArtifactUrl(/data_safety\.csv/, base, files); - } - - if (targets?.match('play-listing')) { - artifacts[ARTIFACT_PLAY_LISTING] = getArtifactUrl(/play-listing\/index\.html/, base, files); - artifacts[ARTIFACT_PLAY_LISTING_MANIFEST] = getArtifactUrl( - /play-listing\/manifest\.json/, - base, - files - ); - artifacts[ARTIFACT_VERSION_CODE] = getArtifactUrl(/version_code\.txt/, base, files); - artifacts[ARTIFACT_PACKAGE_NAME] = getArtifactUrl(/package_name\.txt/, base, files); - artifacts[ARTIFACT_PUBLISH_PROPERTIES] = getArtifactUrl( - /publish-properties\.json/, - base, - files - ); - artifacts[ARTIFACT_WHATS_NEW] = getArtifactUrl(/whats_new\.txt/, base, files); - } - - if (targets?.match('play-html')) { - artifacts[ARTIFACT_HTML] = getArtifactUrl(/html\.zip/, base, files); - } - - if (targets?.match('pwa')) { - artifacts[ARTIFACT_PWA] = getArtifactUrl(/pwa\.zip/, base, files); - } - - if (targets?.match('asset-package')) { - artifacts[ARTIFACT_ASSET_PACKAGE] = getArtifactUrl(/asset-package\/.*\.zip/, base, files); - artifacts[ARTIFACT_PACKAGE_NAME] = getArtifactUrl(/package_name\.txt/, base, files); - artifacts[ARTIFACT_ASSET_PREVIEW] = getArtifactUrl(/asset-package\/preview\.html/, base, files); - artifacts[ARTIFACT_ASSET_NOTIFY] = getArtifactUrl(/asset-package\/notify\.json/, base, files); - } - - return artifacts; -} - -export function releaseArtifacts( - release: Prisma.releaseGetPayload<{ - select: { - artifact_url_base: true; - console_text_url: true; - artifact_files: true; - }; - }> -) { - const { artifact_url_base: base, artifact_files: files } = release; - return { - [ARTIFACT_VERSION]: getArtifactUrl(/version\.json/, base, files), - [ARTIFACT_CLOUD_WATCH]: release.console_text_url, - [ARTIFACT_PUBLISH_URL]: getArtifactUrl(/console\.log/, base, files) - } as Record; -} - export function getArtifactUrl( pattern: RegExp, base: string | null, @@ -120,20 +9,17 @@ export function getArtifactUrl( ) { const filename = getArtifactFilename(pattern, artifact_files); if (filename) { - return (base ?? '') + encodeFilename(filename); + return (base ?? '') + encodeURI(filename); } return undefined; } -function encodeFilename(filename: string) { - return encodeURI(filename); -} export function getArtifactFilename(pattern: RegExp, artifact_files: string | null) { return artifact_files?.split(',').find((f) => f.match(pattern)) ?? null; } -function getArtfactFilenameCount(pattern: RegExp, artifact_files: string | null) { +export function getArtfactFilenameCount(pattern: RegExp, artifact_files: string | null) { return artifact_files?.split(',').filter((f) => f.match(pattern)).length ?? 0; } -function getArtifactUrls( +export function getArtifactUrls( pattern: RegExp, artifact_url_base: string | null, artifact_files: string | null @@ -141,7 +27,7 @@ function getArtifactUrls( const urls = artifact_files ?.split(',') .filter((f) => f.match(pattern)) - .map((f) => (artifact_url_base ?? '') + encodeFilename(f)); + .map((f) => (artifact_url_base ?? '') + encodeURI(f)); return urls?.length ? urls : null; } diff --git a/src/lib/models/build.ts b/src/lib/models/build.ts index 4701564f..613ca100 100644 --- a/src/lib/models/build.ts +++ b/src/lib/models/build.ts @@ -1,4 +1,6 @@ +import type { Prisma } from '@prisma/client'; import { basename, extname } from 'node:path'; +import { getArtfactFilenameCount, getArtifactUrl, getArtifactUrls } from './artifacts'; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Build { @@ -98,4 +100,80 @@ export namespace Build { return [type, file]; } + + export function artifacts( + build: Prisma.buildGetPayload<{ + select: { + targets: true; + artifact_url_base: true; + console_text_url: true; + artifact_files: true; + }; + }> + ) { + const { targets, artifact_url_base: base, artifact_files: files } = build; + // We need to at least have one artifact or the current Portal will fail to parse the JSON. + // TODO: Treat these like the others once Portal is fixed. + const artifacts = { + [Artifact.Version]: getArtifactUrl(/version\.json/, base, files), + [Artifact.CloudWatch]: build.console_text_url, + // We used to return consoleText as the first item that matched "\.log" and we REALLY want console.log + // which is generated by BuildEngine. There is a APK_NAME-output.log which doesn't have everything in the log. + [Artifact.ConsoleText]: getArtifactUrl(/console\.log/, base, files), + [Artifact.PublishProperties]: getArtifactUrl(/publish-properties\.json/, base, files) + } as Record; + if (targets?.match('apk')) { + artifacts[Artifact.AAB] = getArtifactUrl(/\.aab/, base, files); + const count = getArtfactFilenameCount(/\.apk/, files); + if (count > 1) { + for (const apk of getArtifactUrls(/\.apk/, base, files) ?? []) { + const matches = apk.match(/-([^.]+)\.apk/); + artifacts[matches?.at(1) + '-' + Artifact.APK] = apk; + } + } else { + artifacts[Artifact.APK] = getArtifactUrl(/\.apk/, base, files); + } + artifacts[Artifact.EncryptedKey] = getArtifactUrl(/_key\.pepk/, base, files); + artifacts[Artifact.About] = getArtifactUrl(/about\.txt/, base, files); + artifacts[Artifact.DataSafetyCsv] = getArtifactUrl(/data_safety\.csv/, base, files); + } + + if (targets?.match('play-listing')) { + artifacts[Artifact.PlayListing] = getArtifactUrl(/play-listing\/index\.html/, base, files); + artifacts[Artifact.PlayListingManifest] = getArtifactUrl( + /play-listing\/manifest\.json/, + base, + files + ); + artifacts[Artifact.VersionCode] = getArtifactUrl(/version_code\.txt/, base, files); + artifacts[Artifact.PackageName] = getArtifactUrl(/package_name\.txt/, base, files); + artifacts[Artifact.PublishProperties] = getArtifactUrl( + /publish-properties\.json/, + base, + files + ); + artifacts[Artifact.WhatsNew] = getArtifactUrl(/whats_new\.txt/, base, files); + } + + if (targets?.match('play-html')) { + artifacts[Artifact.HTML] = getArtifactUrl(/html\.zip/, base, files); + } + + if (targets?.match('pwa')) { + artifacts[Artifact.PWA] = getArtifactUrl(/pwa\.zip/, base, files); + } + + if (targets?.match('asset-package')) { + artifacts[Artifact.AssetPackage] = getArtifactUrl(/asset-package\/.*\.zip/, base, files); + artifacts[Artifact.PackageName] = getArtifactUrl(/package_name\.txt/, base, files); + artifacts[Artifact.AssetPreview] = getArtifactUrl( + /asset-package\/preview\.html/, + base, + files + ); + artifacts[Artifact.AssetNotify] = getArtifactUrl(/asset-package\/notify\.json/, base, files); + } + + return artifacts; + } } diff --git a/src/lib/models/release.ts b/src/lib/models/release.ts index f056d12a..be7eaf22 100644 --- a/src/lib/models/release.ts +++ b/src/lib/models/release.ts @@ -1,4 +1,6 @@ +import type { Prisma } from '@prisma/client'; import { basename, extname } from 'node:path'; +import { getArtifactUrl } from './artifacts'; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Release { @@ -31,4 +33,21 @@ export namespace Release { } return [type, file]; } + + export function artifacts( + release: Prisma.releaseGetPayload<{ + select: { + artifact_url_base: true; + console_text_url: true; + artifact_files: true; + }; + }> + ) { + const { artifact_url_base: base, artifact_files: files } = release; + return { + [Release.Artifact.CloudWatch]: release.console_text_url, + [Release.Artifact.ConsoleText]: getArtifactUrl(/console\.log/, base, files), + [Release.Artifact.PublishUrl]: getArtifactUrl(/publish_url\.txt/, base, files) + } as Record; + } } diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts index bcfad890..7542b1ab 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts @@ -1,8 +1,8 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; +import { Build } from '$lib/models/build'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; -import { artifacts } from '$lib/models/artifacts'; const buildSchema = v.strictObject({ targets: v.string(), @@ -43,7 +43,7 @@ export const POST: RequestHandler = async ({ request, params }) => { return new Response( JSON.stringify({ ...build, - artifacts: artifacts(build), + artifacts: Build.artifacts(build), client_id: undefined, artifact_url_base: undefined, console_text_url: undefined, diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts index 77b1e2e8..95410bf8 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts @@ -1,8 +1,9 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; +import { Build } from '$lib/models/build'; +import { Release } from '$lib/models/release'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; -import { artifacts, releaseArtifacts } from '$lib/models/artifacts'; // GET /job/[id]/build/[id] export const GET: RequestHandler = async ({ params }) => { @@ -36,7 +37,7 @@ export const GET: RequestHandler = async ({ params }) => { return new Response( JSON.stringify({ ...build, - artifacts: artifacts(build), + artifacts: Build.artifacts(build), artifact_url_base: undefined, console_text_url: undefined, artifact_files: undefined, @@ -110,7 +111,7 @@ export const PUT: RequestHandler = async ({ request, params }) => { return new Response( JSON.stringify({ ...release, - artifacts: releaseArtifacts(release), + artifacts: Release.artifacts(release), artifact_url_base: undefined, console_text_url: undefined, artifact_files: undefined, diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts index 9a2d8649..f00bf3bd 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts @@ -1,7 +1,7 @@ import type { RequestHandler } from './$types'; +import { Release } from '$lib/models/release'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; -import { releaseArtifacts } from '$lib/models/artifacts'; // GET /job/[id]/build/[id]/release/[id] export const GET: RequestHandler = async ({ params }) => { @@ -44,7 +44,7 @@ export const GET: RequestHandler = async ({ params }) => { return new Response( JSON.stringify({ ...release, - artifacts: releaseArtifacts(release), + artifacts: Release.artifacts(release), artifact_url_base: undefined, console_text_url: undefined, artifact_files: undefined, diff --git a/src/routes/build-admin/view/+page.svelte b/src/routes/build-admin/view/+page.svelte index b7d8642d..f27c44d0 100644 --- a/src/routes/build-admin/view/+page.svelte +++ b/src/routes/build-admin/view/+page.svelte @@ -3,7 +3,7 @@ import { page } from '$app/state'; import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import { title } from '$lib/stores'; - import { artifacts } from '$lib/models/artifacts'; + import { Build } from '$lib/models/build'; import { getTimeDateString } from '$lib/utils/time'; $title = 'View Build: ' + page.url.searchParams.get('id')!; @@ -59,7 +59,7 @@ Artifacts {#if data.build.artifact_files} - {#each Object.entries(artifacts(data.build)) + {#each Object.entries(Build.artifacts(data.build)) .filter(([_, url]) => !!url) .sort(([a, _1], [b, _2]) => a.localeCompare(b, 'en-US')) as [name, url]} {name} diff --git a/src/routes/release-admin/view/+page.svelte b/src/routes/release-admin/view/+page.svelte index 5be8f2ef..40bc6cbc 100644 --- a/src/routes/release-admin/view/+page.svelte +++ b/src/routes/release-admin/view/+page.svelte @@ -5,6 +5,7 @@ import { title } from '$lib/stores'; import { getArtifactUrl } from '$lib/models/artifacts'; import { getTimeDateString } from '$lib/utils/time'; + import { Release } from '$lib/models/release'; $title = 'View Release: ' + page.url.searchParams.get('id')!; @@ -64,29 +65,15 @@ Artifacts - cloudWatch - ,  - - consoleText - - ,  - - publishUrl - + + {#if data.release.artifact_files} + {#each Object.entries(Release.artifacts(data.release)) + .filter(([_, url]) => !!url) + .sort(([a, _1], [b, _2]) => a.localeCompare(b, 'en-US')) as [name, url]} + {name} + ,  + {/each} + {/if} From 797311b56636301f989e88f390b416804e29204d Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 23 Oct 2025 15:25:38 -0500 Subject: [PATCH 077/144] Fix lint errors --- src/lib/server/actions/release.ts | 2 +- src/lib/server/aws/common.ts | 1 - src/routes/build-admin/view/+page.svelte | 2 +- src/routes/release-admin/view/+page.svelte | 4 +--- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/server/actions/release.ts b/src/lib/server/actions/release.ts index da21fc1d..9be70fbc 100644 --- a/src/lib/server/actions/release.ts +++ b/src/lib/server/actions/release.ts @@ -1,8 +1,8 @@ import type { Prisma } from '@prisma/client'; import { readFile } from 'node:fs/promises'; -import { CodeBuild, type ReleaseForCodeBuild } from '../aws/codebuild'; import { Build } from '../../models/build'; import { Release } from '../../models/release'; +import { CodeBuild, type ReleaseForCodeBuild } from '../aws/codebuild'; import { prisma } from '../prisma'; import { Utils } from '../utils'; diff --git a/src/lib/server/aws/common.ts b/src/lib/server/aws/common.ts index 342ba903..93737807 100644 --- a/src/lib/server/aws/common.ts +++ b/src/lib/server/aws/common.ts @@ -1,4 +1,3 @@ -import type { Prisma } from '@prisma/client'; import { env } from '$env/dynamic/private'; export class AWSCommon { diff --git a/src/routes/build-admin/view/+page.svelte b/src/routes/build-admin/view/+page.svelte index f27c44d0..688e6a45 100644 --- a/src/routes/build-admin/view/+page.svelte +++ b/src/routes/build-admin/view/+page.svelte @@ -2,8 +2,8 @@ import type { PageData } from './$types'; import { page } from '$app/state'; import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; - import { title } from '$lib/stores'; import { Build } from '$lib/models/build'; + import { title } from '$lib/stores'; import { getTimeDateString } from '$lib/utils/time'; $title = 'View Build: ' + page.url.searchParams.get('id')!; diff --git a/src/routes/release-admin/view/+page.svelte b/src/routes/release-admin/view/+page.svelte index 40bc6cbc..6c664e66 100644 --- a/src/routes/release-admin/view/+page.svelte +++ b/src/routes/release-admin/view/+page.svelte @@ -2,10 +2,9 @@ import type { PageData } from './$types'; import { page } from '$app/state'; import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; + import { Release } from '$lib/models/release'; import { title } from '$lib/stores'; - import { getArtifactUrl } from '$lib/models/artifacts'; import { getTimeDateString } from '$lib/utils/time'; - import { Release } from '$lib/models/release'; $title = 'View Release: ' + page.url.searchParams.get('id')!; @@ -65,7 +64,6 @@ Artifacts - {#if data.release.artifact_files} {#each Object.entries(Release.artifacts(data.release)) .filter(([_, url]) => !!url) From a745de9b4f171d6c25670d1005fb5dd45a5ac0f3 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 23 Oct 2025 15:25:51 -0500 Subject: [PATCH 078/144] Build task --- src/lib/server/actions/build.ts | 219 ------------------------ src/lib/server/bullmq/types.ts | 8 +- src/lib/server/job-executors/build.ts | 136 ++++++++++++++- src/lib/server/job-executors/polling.ts | 87 +++++++++- 4 files changed, 221 insertions(+), 229 deletions(-) delete mode 100644 src/lib/server/actions/build.ts diff --git a/src/lib/server/actions/build.ts b/src/lib/server/actions/build.ts deleted file mode 100644 index c4f12d2c..00000000 --- a/src/lib/server/actions/build.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { Prisma } from '@prisma/client'; -import { readFile } from 'node:fs/promises'; -import { type BuildForCodeBuild, CodeBuild } from '../aws/codebuild'; -import { CodeCommit } from '../aws/codecommit'; -import { Build } from '../../models/build'; -import { prisma } from '../prisma'; -import { Utils } from '../utils'; - -export class ManageBuildsAction { - public performAction() { - const prefix = Utils.getPrefix(); - console.log(`[${prefix}] ManageBuilds Action start`); - // for all builds where status != complete - // if initialized, try start build - // else check status - } - - /** - * Try to start a build. If it starts, then update the database. - * @param Build build - */ - private async tryStartBuild( - build: BuildForCodeBuild & { - job: Prisma.jobGetPayload<{ select: { existing_version_code: true; git_url: true } }>; - } - ) { - try { - const prefix = Utils.getPrefix(); - console.log(`[${prefix}] tryStartBuild: Starting Build of ${build.jobName()}`); - - // Find the repo and commit id to be built - const job = build.job; - - // Don't start job if a job for this build is currently running - const builds = Build.findAllRunningByJobId(job.id); - if (builds.length > 0) { - console.log('...is currentlyBuilding so wait'); - return; - } - const gitUrl = job.git_url; - // Check to see if codebuild project - const codeCommitProject = gitUrl.startsWith('ssh://'); - if (codeCommitProject) { - // Left this block intact to make it easier to remove when codecommit is not supported - const codecommit = new CodeCommit(); - const branch = 'master'; - const repoUrl = await codecommit.getSourceURL(gitUrl); - const commitId = await codecommit.getCommitId(gitUrl, branch); - - const script = (await readFile('scripts/appbuilders_build.yml')).toString(); - // Start the build - const codeBuild = new CodeBuild(); - const versionCode = (await this.getVersionCode(job)) + 1; - const lastBuildGuid = await codeBuild.startBuild( - repoUrl, - commitId, - build, - script, - versionCode, - codeCommitProject - ); - if (lastBuildGuid) { - console.log(`[${prefix}] Launched Build LastBuildNumber=${lastBuildGuid}`); - await prisma.build.update({ - where: { id: build.id }, - data: { - build_guid: lastBuildGuid, - codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), - console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), - status: Build.Status.Active - } - }); - } - } else { - const script = (await readFile('scripts/appbuilders_s3_build.yml')).toString(); - // Start the build - const codeBuild = new CodeBuild(); - const commitId = ''; // TODO: Remove when git is removed - const versionCode = await this.getVersionCode(job); - const lastBuildGuid = await codeBuild.startBuild( - gitUrl, - commitId, - build, - script, - versionCode, - codeCommitProject - ); - if (lastBuildGuid) { - console.log(`[${prefix}] Launched Build LastBuildNumber=${lastBuildGuid}`); - await prisma.build.update({ - where: { id: build.id }, - data: { - build_guid: lastBuildGuid, - codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), - console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), - status: Build.Status.Active - } - }); - } - } - } catch (e) { - console.log(`[${Utils.getPrefix()}] tryStartBuild: Exception:${e}`); - this.failBuild(build.id); - } - } - - /** - * - * @param Build build - */ - private async checkBuildStatus( - build: Prisma.buildGetPayload<{ - select: { - id: true; - build_guid: true; - status: true; - result: true; - job: { select: { id: true } }; - }; - }> - ) { - try { - const prefix = Utils.getPrefix(); - console.log(`[${prefix}] checkBuildStatus: Check Build of ${build.jobName()}`); - - const job = build.job; - if (job) { - const codeBuild = new CodeBuild(); - const buildStatus = await codeBuild.getBuildStatus( - build.build_guid!, - CodeBuild.getCodeBuildProjectName('build_app') - ); - const phase = buildStatus?.currentPhase; - let status = buildStatus?.buildStatus; - console.log(` phase: ${phase} status: ${status}`); - if (codeBuild.isBuildComplete(buildStatus)) { - console.log(' Build Complete'); - } else { - console.log(' Build Incomplete'); - } - - let savedStatus = build.status; - let savedResult = build.result; - - if (codeBuild.isBuildComplete(buildStatus)) { - savedStatus = Build.Status.PostProcessing; - status = codeBuild.getStatus(buildStatus); - switch (status) { - case CodeBuild.Status.Failed: - case CodeBuild.Status.Fault: - case CodeBuild.Status.TimedOut: - savedResult = Build.Result.Failure; - this.handleFailure(build); - break; - case CodeBuild.Status.Stopped: - savedResult = Build.Result.Aborted; - this.handleFailure(build); - break; - case CodeBuild.Status.Succeeded: - OperationQueue.findOrCreate(OperationQueue.SAVETOS3, build.id, 'build'); - break; - } - } - const saved = await prisma.build.update({ - where: { id: build.id }, - data: { status: savedStatus, result: savedResult } - }); - if (!saved) { - throw new Error( - `Unable to update Build entry, model errors: ${JSON.stringify(build.getFirstErrors(), null, 4)}` - ); - } - const log = Build.getlogBuildDetails(build); - log['job id'] = job.id; - console.log( - `Job=${job.id}, Build=${build.build_guid}, Status=${saved.status}, Result=${saved.result}` - ); - } - } catch (e) { - console.log(`[${Utils.getPrefix()}] checkBuildStatus: Exception:${e}`); - this.failBuild(build.id); - } - } - - private handleFailure(build: unknown) { - build.error = build.cloudWatch(); - const build_id = build.id; - const task = OperationQueue.SAVEERRORTOS3; - OperationQueue.findOrCreate(task, build_id, 'build'); - } - private async getVersionCode( - job: Prisma.jobGetPayload<{ select: { id: true; existing_version_code: true } }> - ) { - const build = await prisma.build.aggregate({ - where: { - job_id: job.id, - status: Build.Status.Completed, - result: Build.Result.Success - }, - _max: { - version_code: true - } - }); - return build._max.version_code ?? job.existing_version_code ?? 0; - } - private async failBuild(id: number) { - try { - await prisma.build.update({ - where: { id }, - data: { - result: Build.Result.Failure, - status: Build.Status.Completed - } - }); - } catch (e) { - console.log(`[${Utils.getPrefix()}] failBuild Exception: ${e}`); - } - } -} diff --git a/src/lib/server/bullmq/types.ts b/src/lib/server/bullmq/types.ts index 73791451..850b6f45 100644 --- a/src/lib/server/bullmq/types.ts +++ b/src/lib/server/bullmq/types.ts @@ -43,9 +43,7 @@ export enum JobType { export namespace Build { export interface Product { type: JobType.Build_Product; - productId: string; - defaultTargets: string; - environment: Record; + buildId: number; } export interface PostProcess { @@ -59,11 +57,7 @@ export namespace Build { export namespace Polling { export interface Build { type: JobType.Poll_Build; - organizationId: number; - productId: string; - jobId: number; buildId: number; - productBuildId: number; } export interface Publish { diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index a6ce5b9b..29584728 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -1,10 +1,144 @@ +import type { Prisma } from '@prisma/client'; import type { Job } from 'bullmq'; +import { readFile } from 'node:fs/promises'; +import { Build } from '../../models/build'; +import { CodeBuild } from '../aws/codebuild'; +import { CodeCommit } from '../aws/codecommit'; import type { BullMQ } from '../bullmq'; +import { prisma } from '../prisma'; export async function product(job: Job): Promise { - return; + try { + const build = await prisma.build.findUniqueOrThrow({ + where: { + id: job.data.buildId + }, + include: { + job: true + } + }); + job.updateProgress(10); + + // Don't start job if a job for this build is currently running + const builds = await prisma.build.count({ + where: { + job_id: build.job_id, + status: { in: [Build.Status.Active, Build.Status.PostProcessing] } + } + }); + if (builds > 0) { + job.log('Existing active builds found. Build cancelled'); + // TODO retry after??? + return { existing: builds }; + } + const gitUrl = build.job.git_url; + // Check to see if codebuild project + const codeCommitProject = gitUrl.startsWith('ssh://'); + if (codeCommitProject) { + job.log('Starting build with CodeCommit'); + // Left this block intact to make it easier to remove when codecommit is not supported + const codecommit = new CodeCommit(); + const branch = 'master'; + const repoUrl = await codecommit.getSourceURL(gitUrl); + if (!repoUrl) throw new Error('No repoUrl found!'); + const commitId = await codecommit.getCommitId(gitUrl, branch); + if (!commitId) throw new Error('No commitId found!'); + job.updateProgress(25); + + const script = (await readFile('scripts/appbuilders_build.yml')).toString(); + job.updateProgress(50); + // Start the build + const codeBuild = new CodeBuild(); + const versionCode = (await getVersionCode(build.job)) + 1; + const lastBuildGuid = await codeBuild.startBuild( + repoUrl, + commitId, + build, + script, + versionCode, + codeCommitProject + ); + job.updateProgress(75); + if (lastBuildGuid) { + await prisma.build.update({ + where: { id: build.id }, + data: { + build_guid: lastBuildGuid, + codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), + console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), + status: Build.Status.Active + } + }); + } + job.updateProgress(100); + return { + repoUrl, + commitId, + versionCode, + lastBuildGuid + }; + } else { + job.log('Starting build with CodeBuild'); + const script = (await readFile('scripts/appbuilders_s3_build.yml')).toString(); + job.updateProgress(50); + // Start the build + const codeBuild = new CodeBuild(); + const commitId = ''; // TODO: Remove when git is removed + const versionCode = await getVersionCode(build.job); // Is there a reason this is not incremented here?? + const lastBuildGuid = await codeBuild.startBuild( + gitUrl, + commitId, + build, + script, + versionCode, + codeCommitProject + ); + job.updateProgress(75); + if (lastBuildGuid) { + await prisma.build.update({ + where: { id: build.id }, + data: { + build_guid: lastBuildGuid, + codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), + console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), + status: Build.Status.Active + } + }); + } + job.updateProgress(100); + return { + versionCode, + lastBuildGuid + }; + } + } catch (e) { + job.log(`${e}`); + await prisma.build.update({ + where: { id: job.data.buildId }, + data: { + result: Build.Result.Failure, + status: Build.Status.Completed + } + }); + } } export async function postProcess(job: Job): Promise { return; } + +async function getVersionCode( + job: Prisma.jobGetPayload<{ select: { id: true; existing_version_code: true } }> +) { + const build = await prisma.build.aggregate({ + where: { + job_id: job.id, + status: Build.Status.Completed, + result: Build.Result.Success + }, + _max: { + version_code: true + } + }); + return build._max.version_code ?? job.existing_version_code ?? 0; +} diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index e8c09673..83fc6c94 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -1,10 +1,93 @@ +import type { Prisma } from '@prisma/client'; import type { Job } from 'bullmq'; -import type { BullMQ } from '../bullmq'; +import { Build } from '../../models/build'; +import { CodeBuild } from '../aws/codebuild'; +import { BullMQ, getQueues } from '../bullmq'; +import { prisma } from '../prisma'; export async function build(job: Job): Promise { - return; + try { + const build = await prisma.build.findUnique({ + where: { id: job.data.buildId }, + include: { job: true } + }); + + if (build?.job) { + const codeBuild = new CodeBuild(); + const buildStatus = await codeBuild.getBuildStatus( + build.build_guid!, + CodeBuild.getCodeBuildProjectName('build_app') + ); + const phase = buildStatus?.currentPhase; + let status = buildStatus?.buildStatus; + job.log(` phase: ${phase} status: ${status}`); + if (codeBuild.isBuildComplete(buildStatus)) { + job.log(' Build Complete'); + } else { + job.log(' Build Incomplete'); + } + + job.updateProgress(50); + + if (codeBuild.isBuildComplete(buildStatus)) { + build.status = Build.Status.PostProcessing; + status = codeBuild.getStatus(buildStatus); + switch (status) { + case CodeBuild.Status.Failed: + case CodeBuild.Status.Fault: + case CodeBuild.Status.TimedOut: + build.result = Build.Result.Failure; + await handleFailure(build); + break; + case CodeBuild.Status.Stopped: + build.result = Build.Result.Aborted; + await handleFailure(build); + break; + case CodeBuild.Status.Succeeded: + await getQueues().S3.add(`Save Build ${job.data.buildId} to S3`, { + type: BullMQ.JobType.S3_CopyArtifacts, + scope: 'build', + id: job.data.buildId + }); + break; + } + } + await prisma.build.update({ + where: { id: build.id }, + data: { ...build, job: undefined } + }); + job.updateProgress(100); + return { + status: build.status, + guid: build.build_guid, + result: build.result, + url_base: build.artifact_url_base, + files: build.artifact_files + }; + } + } catch (e) { + job.log(`${e}`); + await prisma.build.update({ + where: { id: job.data.buildId }, + data: { + result: Build.Result.Failure, + status: Build.Status.Completed + } + }); + } } export async function publish(job: Job): Promise { return; } + +async function handleFailure( + build: Prisma.buildGetPayload<{ select: { error: true; id: true; console_text_url: true } }> +) { + build.error = build.console_text_url; + await getQueues().S3.add(`Save Errors for Build ${build.id} to S3`, { + type: BullMQ.JobType.S3_CopyError, + scope: 'build', + id: build.id + }); +} From 172a15de90b7856f074cad83253f460e3148eb38 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 23 Oct 2025 16:48:55 -0500 Subject: [PATCH 079/144] Add queue prefix so BuildEngine can share valkey --- src/lib/server/bullmq/queues.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/server/bullmq/queues.ts b/src/lib/server/bullmq/queues.ts index 5436b573..149b44a9 100644 --- a/src/lib/server/bullmq/queues.ts +++ b/src/lib/server/bullmq/queues.ts @@ -61,14 +61,16 @@ export const QueueConnected = () => _queueConnection?.IsConnected() ?? false; export const getWorkerConfig = () => { if (!_workerConnection) _workerConnection = new Connection(false); return { - connection: _workerConnection!.connection() + connection: _workerConnection!.connection(), + prefix: 'build-engine' } as const; }; export const getQueueConfig = () => { if (!_queueConnection) _queues = createQueues(); return { - connection: _queueConnection!.connection() + connection: _queueConnection!.connection(), + prefix: 'build-engine' } as const; }; let _queues: ReturnType | undefined = undefined; From b61078ade4f9b6001431f3d2f6ef47374c0e00bc Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 09:06:04 -0500 Subject: [PATCH 080/144] Fix find-and-replace in STS --- src/lib/server/aws/sts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/server/aws/sts.ts b/src/lib/server/aws/sts.ts index 1af96228..6e9f9b68 100644 --- a/src/lib/server/aws/sts.ts +++ b/src/lib/server/aws/sts.ts @@ -114,7 +114,7 @@ export class STS extends AWSCommon { ) { const path = project.url!.substring(5); return policy - .replace('BUCKET', path.split('/')[0]) - .replace('FOLDER', path.split('/').slice(1).join('/')); + .replace(/BUCKET/g, path.split('/')[0]) + .replace(/FOLDER/g, path.split('/').slice(1).join('/')); } } From 5a1717a2cff323806016c60706e629266e0bed1c Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 09:06:54 -0500 Subject: [PATCH 081/144] Fix project creation --- src/routes/(api)/project/+server.ts | 36 ++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/routes/(api)/project/+server.ts b/src/routes/(api)/project/+server.ts index 317120a7..3bf80fe6 100644 --- a/src/routes/(api)/project/+server.ts +++ b/src/routes/(api)/project/+server.ts @@ -1,5 +1,7 @@ +import type { Prisma } from '@prisma/client'; import * as v from 'valibot'; import type { RequestHandler } from './$types'; +import { AWSCommon } from '$lib/server/aws/common'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; @@ -23,13 +25,26 @@ export const POST: RequestHandler = async ({ request, locals }) => { const parsed = v.safeParse(projectSchema, await request.json()); if (!parsed.success) return ErrorResponse(400, JSON.stringify(v.flatten(parsed.issues))); // TODO enqueue project creation job + const withoutStorage = { ...parsed.output, storage_type: undefined }; const project = await prisma.project.create({ - data: { ...parsed.output, status: 'initialized', client_id: locals.clientId } + data: { + ...withoutStorage, + status: 'completed', + result: 'SUCCESS', + client_id: locals.clientId + }, + include: { + client: true + } }); + const url = `s3://${AWSCommon.getProjectsBucket()}/${getS3Folder(project)}`; + await prisma.project.update({ where: { id: project.id }, data: { url } }); return new Response( JSON.stringify({ ...project, + url, client_id: undefined, + client: undefined, _links: { self: { href: `${process.env.ORIGIN || 'http://localhost:8443'}/project/${project.id}` @@ -38,6 +53,25 @@ export const POST: RequestHandler = async ({ request, locals }) => { }) ); }; +// TODO create bucket??? +function getS3Folder( + project: Prisma.projectGetPayload<{ + select: { + client_id: true; + app_id: true; + language_code: true; + id: true; + project_name: true; + client: { select: { prefix: true } }; + }; + }> +) { + const s3client = project.client ? project.client.prefix + '/' : ''; + const s3folder = `${project.language_code}-${project.id}-${project.project_name}` + .replace(' ', '-') + .replace(/[^a-zA-Z0-9-]/, ''); + return `${s3client}${project.app_id}/${s3folder}`; +} // GET /project export const GET: RequestHandler = async () => { From d9213d652e3e0f2540a6354fcda7005924291b34 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 09:07:08 -0500 Subject: [PATCH 082/144] Add Build to queue --- src/routes/(api)/job/[jobId=idNumber]/build/+server.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts index 7542b1ab..1ef75a18 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts @@ -1,6 +1,7 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; import { Build } from '$lib/models/build'; +import { BullMQ, getQueues } from '$lib/server/bullmq'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; @@ -40,6 +41,10 @@ export const POST: RequestHandler = async ({ request, params }) => { artifact_files: true } }); + await getQueues().Builds.add(`Start Build #${build.id} for Job ${build.job_id}`, { + type: BullMQ.JobType.Build_Product, + buildId: build.id + }); return new Response( JSON.stringify({ ...build, From d667e381eb26ef9101810e2bf1b9b7e8e8f7d099 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 09:11:31 -0500 Subject: [PATCH 083/144] Write error to DB from try/catch --- src/lib/server/job-executors/build.ts | 3 ++- src/lib/server/job-executors/polling.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index 29584728..671d1c17 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -117,7 +117,8 @@ export async function product(job: Job): Promise where: { id: job.data.buildId }, data: { result: Build.Result.Failure, - status: Build.Status.Completed + status: Build.Status.Completed, + error: String(e) } }); } diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index 83fc6c94..7b162973 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -71,7 +71,8 @@ export async function build(job: Job): Promise { where: { id: job.data.buildId }, data: { result: Build.Result.Failure, - status: Build.Status.Completed + status: Build.Status.Completed, + error: String(e) } }); } From 2492331b066b27befb2961b6c8d441f36d75c3e4 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 09:30:21 -0500 Subject: [PATCH 084/144] Read scripts from correct location --- scripts/appbuilders_build.yml | 66 +++++++++++++++++++++++++++ scripts/appbuilders_publish.yml | 46 +++++++++++++++++++ scripts/appbuilders_s3_build.yml | 46 +++++++++++++++++++ src/lib/server/job-executors/build.ts | 9 +++- 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 scripts/appbuilders_build.yml create mode 100644 scripts/appbuilders_publish.yml create mode 100644 scripts/appbuilders_s3_build.yml diff --git a/scripts/appbuilders_build.yml b/scripts/appbuilders_build.yml new file mode 100644 index 00000000..d8070e8f --- /dev/null +++ b/scripts/appbuilders_build.yml @@ -0,0 +1,66 @@ +version: 0.2 + +env: + variables: + "PUBLISHER" : "wycliffeusa" + "BUILD_NUMBER" : "0" + "VERSION_CODE" : "42" + "APP_BUILDER_SCRIPT_PATH" : "scripture-app-builder" + "SECRETS_BUCKET": "sil-prd-aps-secrets" + "SECRETS_DIR" : "/secrets" + +phases: + install: + commands: + pre_build: + commands: + - OUTPUT_DIR="/${BUILD_NUMBER}" + - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build/google_play_store/${PUBLISHER}" + - mkdir "${SECRETS_DIR}" + - mkdir "${OUTPUT_DIR}" + - aws s3 sync "${SECRETS_S3}" "${SECRETS_DIR}" + - export KSP=$(cat "${SECRETS_DIR}/ksp.txt") + - export KA=$(cat "${SECRETS_DIR}/ka.txt") + - export KAP=$(cat "${SECRETS_DIR}/kap.txt") + - export GRADLE_OPTS="-Dorg.gradle.daemon=false" + build: + commands: + - KS="${SECRETS_DIR}/${PUBLISHER}.keystore" + - echo "BUILD_NUMBER=${BUILD_NUMBER}" + - echo "VERSION_CODE=${VERSION_CODE}" + - OUTPUT_DIR="/${BUILD_NUMBER}" + - PROJNAME=$(basename *.appDef .appDef) + - mv "${PROJNAME}.appDef" build.appDef + - mv "${PROJNAME}_data" build_data + - APPDEF_VERSION=$(grep "version code=" build.appDef|awk -F"\"" '{print $2}') + - echo "APPDEF_VERSION=${APPDEF_VERSION}" + - if [ "$APPDEF_VERSION" -ge "$VERSION_CODE" ]; then VERSION_CODE=$((APPDEF_VERSION+1)); fi + - VERSION_NAME=$(dpkg -s scripture-app-builder | grep 'Version' | awk -F '[ +]' '{print $2}') + - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -ks $KS -ksp $KSP -ka $KA -kap $KAP -fp apk.output=$OUTPUT_DIR -vc $VERSION_CODE -vn $VERSION_NAME -ft share-app-link=true + - echo $(awk -F '[<>]' '/package/{print $3}' build.appDef) > $OUTPUT_DIR/package_name.txt + - echo $VERSION_CODE > $OUTPUT_DIR/version_code.txt + - "echo \"{ \\\"version\\\" : \\\"${VERSION_NAME}.${VERSION_CODE}\\\", \\\"versionName\\\" : \\\"${VERSION_NAME}\\\", \\\"versionCode\\\" : \\\"${VERSION_CODE}\\\" } \" > $OUTPUT_DIR/version.json" + - if [ -f "build_data/about/about.txt" ]; then cp build_data/about/about.txt $OUTPUT_DIR/; fi + - PUBLISH_DIR="build_data/publish" + - PLAY_LISTING_DIR="${PUBLISH_DIR}/play-listing" + - LIST_DIR="${PLAY_LISTING_DIR}/" + - MANIFEST_FILE="manifest.txt" + - if [ -f $LIST_DIR$MANIFEST_FILE ]; then rm $LIST_DIR$MANIFEST_FILE; fi; + - FILE_LIST=$(find $PLAY_LISTING_DIR -type f -print) + - for f in $FILE_LIST; do fn=${f#*"$PLAY_LISTING_DIR/"}; echo $fn >> $OUTPUT_DIR/$MANIFEST_FILE; done + - if [ -d "$PLAY_LISTING_DIR" ]; then cp -r "$PLAY_LISTING_DIR" $OUTPUT_DIR; find $OUTPUT_DIR -name whats_new.txt | while read filename; do DIR=$(dirname "${filename}"); cp "$filename" $OUTPUT_DIR; mkdir "${DIR}/changelogs"; mv "$filename" "${DIR}/changelogs/${VERSION_CODE}.txt"; done; fi + - mv build_data "${PROJNAME}_data" + - mv build.appDef "${PROJNAME}.appDef" + #- if [ "$CODEBUILD_BUILD_SUCCEEDING" -gt "0" ]; then git remote -v; git tag $VERSION_CODE; git push origin $VERSION_CODE; fi + #post_build: + #commands: + +artifacts: + files: + - $OUTPUT_DIR/**/* + # - location + #discard-paths: yes + #base-directory: location +#cache: + #paths: + # - paths \ No newline at end of file diff --git a/scripts/appbuilders_publish.yml b/scripts/appbuilders_publish.yml new file mode 100644 index 00000000..f9e258d7 --- /dev/null +++ b/scripts/appbuilders_publish.yml @@ -0,0 +1,46 @@ +version: 0.2 + +env: + variables: + "PUBLISHER" : "wycliffeusa" + "SECRETS_BUCKET": "sil-prd-aps-secrets" + "CHANNEL" : "alpha" + "PROMOTE_FROM" : "" + "ARTIFACTS_S3_DIR" : "s3://dem-aps-artifacts/dem/jobs/build_scriptureappbuilder_1/3" + "SECRETS_DIR" : "/secrets" + "ARTIFACTS_DIR" : "/artifacts" + "SCRIPT_DIR" : "/script" + "SCRIPT_S3" : "s3://s3url/default" + "TARGETS" : "" + "RELEASE_NUMBER" : "0" + +phases: + install: + commands: + pre_build: + commands: + - OUTPUT_DIR="/${RELEASE_NUMBER}" + - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/publish" + - mkdir "${SECRETS_DIR}" + - mkdir "${ARTIFACTS_DIR}" + - mkdir "${SCRIPT_DIR}" + - mkdir "${OUTPUT_DIR}" + - echo "${SCRIPT_S3}" + - aws s3 sync "${ARTIFACTS_S3_DIR}" "${ARTIFACTS_DIR}" + - aws s3 sync "${SCRIPT_S3}" "${SCRIPT_DIR}" + - ls -l "${ARTIFACTS_DIR}" + build: + commands: + - TARGETS="${TARGETS}" bash ${SCRIPT_DIR}/publish.sh + #post_build: + #commands: + +artifacts: + files: + - $OUTPUT_DIR/**/* + # - location + #discard-paths: yes + #base-directory: location +#cache: + #paths: + # - paths \ No newline at end of file diff --git a/scripts/appbuilders_s3_build.yml b/scripts/appbuilders_s3_build.yml new file mode 100644 index 00000000..c534b36e --- /dev/null +++ b/scripts/appbuilders_s3_build.yml @@ -0,0 +1,46 @@ +version: 0.2 + +env: + variables: + "PUBLISHER" : "wycliffeusa" + "BUILD_NUMBER" : "0" + "VERSION_CODE" : "42" + "APP_BUILDER_SCRIPT_PATH" : "scripture-app-builder" + "SECRETS_BUCKET": "sil-prd-aps-secrets" + "SECRETS_DIR" : "/secrets" + "PROJECT_DIR" : "/project" + "SCRIPT_DIR" : "/script" + "PROJECT_S3" : "s3://s3url" + "SCRIPT_S3" : "s3://s3url/default" + "TARGETS" : "" + +phases: + install: + commands: + pre_build: + commands: + - OUTPUT_DIR="/${BUILD_NUMBER}" + - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build" + - mkdir "${SECRETS_DIR}" + - mkdir "${OUTPUT_DIR}" + - mkdir "${PROJECT_DIR}" + - mkdir "${SCRIPT_DIR}" + - echo "PROJECT_S3=${PROJECT_S3}" + - aws s3 sync "${PROJECT_S3}" "${PROJECT_DIR}" + - aws s3 sync "${SCRIPT_S3}" "${SCRIPT_DIR}" + - export GRADLE_OPTS="-Dorg.gradle.daemon=false" + build: + commands: + - TARGETS="${TARGETS}" bash ${SCRIPT_DIR}/build.sh + #post_build: + #commands: + +artifacts: + files: + - $OUTPUT_DIR/**/* + # - location + #discard-paths: yes + #base-directory: location +#cache: + #paths: + # - paths \ No newline at end of file diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index 671d1c17..b6eec724 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -1,6 +1,7 @@ import type { Prisma } from '@prisma/client'; import type { Job } from 'bullmq'; import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; import { Build } from '../../models/build'; import { CodeBuild } from '../aws/codebuild'; import { CodeCommit } from '../aws/codecommit'; @@ -45,7 +46,9 @@ export async function product(job: Job): Promise if (!commitId) throw new Error('No commitId found!'); job.updateProgress(25); - const script = (await readFile('scripts/appbuilders_build.yml')).toString(); + const script = ( + await readFile(join(process.cwd(), './scripts/appbuilders_build.yml')) + ).toString(); job.updateProgress(50); // Start the build const codeBuild = new CodeBuild(); @@ -79,7 +82,9 @@ export async function product(job: Job): Promise }; } else { job.log('Starting build with CodeBuild'); - const script = (await readFile('scripts/appbuilders_s3_build.yml')).toString(); + const script = ( + await readFile(join(process.cwd(), './scripts/appbuilders_s3_build.yml')) + ).toString(); job.updateProgress(50); // Start the build const codeBuild = new CodeBuild(); From 6dbe7b20e1534071a0ab336e04b5f1dce081db8d Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 10:48:29 -0500 Subject: [PATCH 085/144] Use PostgreSQL instead of MariaDB IMPORTANT!: Data will need to be manually migrated from MariaDB to PostgreSQL. I did it using CSV export/import (I did have to manually reset the id sequences). --- deployment/development/docker-compose.yml | 16 +- package.json | 3 - prisma.config.ts | 10 + .../prisma/migrations/00_init/migration.sql | 243 ++++++++---------- .../01_prisma_autodate/migration.sql | 17 -- src/lib/prisma/migrations/migration_lock.toml | 2 +- src/lib/prisma/schema.prisma | 70 ++--- 7 files changed, 141 insertions(+), 220 deletions(-) create mode 100644 prisma.config.ts delete mode 100644 src/lib/prisma/migrations/01_prisma_autodate/migration.sql diff --git a/deployment/development/docker-compose.yml b/deployment/development/docker-compose.yml index 8cb9addc..26e27de2 100644 --- a/deployment/development/docker-compose.yml +++ b/deployment/development/docker-compose.yml @@ -1,19 +1,19 @@ volumes: - mysql-data: + postgres-data: valkey-data: services: db: - image: mariadb:10.11 + image: postgres:17 environment: - MYSQL_DATABASE: development - MYSQL_USER: appbuilder - MYSQL_PASSWORD: 1234 - MYSQL_ROOT_PASSWORD: 1234 + POSTGRES_DB: development + POSTGRES_USER: db-user + POSTGRES_PASSWORD: 1234 + PGDATA: /var/lib/postgresql/data ports: - - "3306:3306" + - "5432:5432" volumes: - - mysql-data:/var/lib/mysql/data + - postgres-data:/var/lib/postgresql/data valkey: image: valkey/valkey:latest diff --git a/package.json b/package.json index e2a56936..1d781948 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,6 @@ "format": "eslint --fix .", "lint": "eslint ." }, - "prisma": { - "schema": "src/lib/prisma/schema.prisma" - }, "devDependencies": { "@bull-board/api": "^6.13.1", "@bull-board/hono": "^6.13.1", diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 00000000..c2b49680 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from '@prisma/config'; +import path from 'path'; +import 'dotenv/config'; + +export default defineConfig({ + schema: path.join('src', 'lib', 'prisma', 'schema.prisma'), + migrations: { + path: path.join('src', 'lib', 'prisma', 'migrations') + } +}); diff --git a/src/lib/prisma/migrations/00_init/migration.sql b/src/lib/prisma/migrations/00_init/migration.sql index 74a49e56..deb2c715 100644 --- a/src/lib/prisma/migrations/00_init/migration.sql +++ b/src/lib/prisma/migrations/00_init/migration.sql @@ -1,156 +1,123 @@ -- CreateTable -CREATE TABLE `build` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `job_id` INTEGER NOT NULL, - `status` VARCHAR(255) NULL, - `result` VARCHAR(255) NULL, - `error` VARCHAR(2083) NULL, - `created` DATETIME(0) NULL, - `updated` DATETIME(0) NULL, - `channel` VARCHAR(255) NULL, - `version_code` INTEGER NULL, - `artifact_url_base` VARCHAR(2083) NULL, - `artifact_files` VARCHAR(4096) NULL, - `build_guid` VARCHAR(255) NULL, - `console_text_url` VARCHAR(255) NULL, - `codebuild_url` VARCHAR(255) NULL, - `targets` VARCHAR(255) NULL, - `environment` TEXT NULL, - - INDEX `fk_build_job_id`(`job_id`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE TABLE "public"."build" ( + "id" SERIAL NOT NULL, + "job_id" INTEGER NOT NULL, + "status" VARCHAR(255), + "result" VARCHAR(255), + "error" VARCHAR(2083), + "created" TIMESTAMP(6), + "updated" TIMESTAMP(6), + "channel" VARCHAR(255), + "version_code" INTEGER, + "artifact_url_base" VARCHAR(2083), + "artifact_files" VARCHAR(4096), + "build_guid" VARCHAR(255), + "console_text_url" VARCHAR(255), + "codebuild_url" VARCHAR(255), + "targets" VARCHAR(255), + "environment" TEXT, + + CONSTRAINT "build_pkey" PRIMARY KEY ("id") +); -- CreateTable -CREATE TABLE `client` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `access_token` VARCHAR(255) NOT NULL, - `prefix` VARCHAR(4) NOT NULL, - `created` DATETIME(0) NULL, - `updated` DATETIME(0) NULL, +CREATE TABLE "public"."client" ( + "id" SERIAL NOT NULL, + "access_token" VARCHAR(255) NOT NULL, + "prefix" VARCHAR(4) NOT NULL, + "created" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + "updated" TIMESTAMP(6), - INDEX `idx_accesS_token`(`access_token`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + CONSTRAINT "client_pkey" PRIMARY KEY ("id") +); -- CreateTable -CREATE TABLE `email_queue` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `to` VARCHAR(255) NOT NULL, - `cc` VARCHAR(255) NULL, - `bcc` VARCHAR(255) NULL, - `subject` VARCHAR(255) NOT NULL, - `text_body` TEXT NULL, - `html_body` TEXT NULL, - `attempts_count` BOOLEAN NULL, - `last_attempt` DATETIME(0) NULL, - `created` DATETIME(0) NULL, - `error` VARCHAR(255) NULL, - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE TABLE "public"."job" ( + "id" SERIAL NOT NULL, + "request_id" VARCHAR(255) NOT NULL, + "git_url" VARCHAR(2083) NOT NULL, + "app_id" VARCHAR(255) NOT NULL, + "publisher_id" VARCHAR(255) NOT NULL, + "created" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + "updated" TIMESTAMP(6), + "client_id" INTEGER, + "existing_version_code" INTEGER DEFAULT 0, + "jenkins_build_url" VARCHAR(1024), + "jenkins_publish_url" VARCHAR(1024), + + CONSTRAINT "job_pkey" PRIMARY KEY ("id") +); -- CreateTable -CREATE TABLE `job` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `request_id` VARCHAR(255) NOT NULL, - `git_url` VARCHAR(2083) NOT NULL, - `app_id` VARCHAR(255) NOT NULL, - `publisher_id` VARCHAR(255) NOT NULL, - `created` DATETIME(0) NULL, - `updated` DATETIME(0) NULL, - `client_id` INTEGER NULL, - `existing_version_code` INTEGER NULL DEFAULT 0, - `jenkins_build_url` VARCHAR(1024) NULL, - `jenkins_publish_url` VARCHAR(1024) NULL, - - INDEX `fk_job_client_id`(`client_id`), - INDEX `idx_request_id`(`request_id`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE TABLE "public"."project" ( + "id" SERIAL NOT NULL, + "status" VARCHAR(255), + "result" VARCHAR(255), + "error" VARCHAR(2083), + "url" VARCHAR(1024), + "user_id" VARCHAR(255), + "group_id" VARCHAR(255), + "app_id" VARCHAR(255), + "project_name" VARCHAR(255), + "language_code" VARCHAR(255), + "publishing_key" VARCHAR(1024), + "created" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + "updated" TIMESTAMP(6), + "client_id" INTEGER, + + CONSTRAINT "project_pkey" PRIMARY KEY ("id") +); -- CreateTable -CREATE TABLE `migration` ( - `version` VARCHAR(180) NOT NULL, - `apply_time` INTEGER NULL, - - PRIMARY KEY (`version`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `operation_queue` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `operation` VARCHAR(255) NOT NULL, - `operation_object_id` INTEGER NULL, - `operation_parms` VARCHAR(2048) NULL, - `attempt_count` INTEGER NOT NULL, - `last_attempt` DATETIME(0) NULL, - `try_after` DATETIME(0) NULL, - `start_time` DATETIME(0) NULL, - `last_error` VARCHAR(2048) NULL, - `created` DATETIME(0) NULL, - `updated` DATETIME(0) NULL, - - INDEX `idx_start_time`(`start_time`), - INDEX `idx_try_after`(`try_after`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `project` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `status` VARCHAR(255) NULL, - `result` VARCHAR(255) NULL, - `error` VARCHAR(2083) NULL, - `url` VARCHAR(1024) NULL, - `user_id` VARCHAR(255) NULL, - `group_id` VARCHAR(255) NULL, - `app_id` VARCHAR(255) NULL, - `project_name` VARCHAR(255) NULL, - `language_code` VARCHAR(255) NULL, - `publishing_key` VARCHAR(1024) NULL, - `created` DATETIME(0) NULL, - `updated` DATETIME(0) NULL, - `client_id` INTEGER NULL, - - INDEX `fk_project_client_id`(`client_id`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `release` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `build_id` INTEGER NOT NULL, - `status` VARCHAR(255) NULL, - `created` DATETIME(0) NULL, - `updated` DATETIME(0) NULL, - `result` VARCHAR(255) NULL, - `error` VARCHAR(2083) NULL, - `channel` VARCHAR(255) NOT NULL, - `title` VARCHAR(30) NULL, - `defaultLanguage` VARCHAR(255) NULL, - `promote_from` VARCHAR(255) NULL, - `build_guid` VARCHAR(255) NULL, - `console_text_url` VARCHAR(255) NULL, - `codebuild_url` VARCHAR(255) NULL, - `targets` VARCHAR(255) NULL, - `environment` TEXT NULL, - `artifact_url_base` VARCHAR(255) NULL, - `artifact_files` VARCHAR(255) NULL, - - INDEX `fk_release_build_id`(`build_id`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE TABLE "public"."release" ( + "id" SERIAL NOT NULL, + "build_id" INTEGER NOT NULL, + "status" VARCHAR(255), + "created" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + "updated" TIMESTAMP(6), + "result" VARCHAR(255), + "error" VARCHAR(2083), + "channel" VARCHAR(255) NOT NULL, + "title" VARCHAR(30), + "defaultLanguage" VARCHAR(255), + "promote_from" VARCHAR(255), + "build_guid" VARCHAR(255), + "console_text_url" VARCHAR(255), + "codebuild_url" VARCHAR(255), + "targets" VARCHAR(255), + "environment" TEXT, + "artifact_url_base" VARCHAR(255), + "artifact_files" VARCHAR(255), + + CONSTRAINT "release_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "idx_build_job_id" ON "public"."build"("job_id"); + +-- CreateIndex +CREATE INDEX "idx_accesS_token" ON "public"."client"("access_token"); + +-- CreateIndex +CREATE INDEX "idx_job_client_id" ON "public"."job"("client_id"); + +-- CreateIndex +CREATE INDEX "idx_request_id" ON "public"."job"("request_id"); + +-- CreateIndex +CREATE INDEX "idx_project_client_id" ON "public"."project"("client_id"); + +-- CreateIndex +CREATE INDEX "idx_release_build_id" ON "public"."release"("build_id"); -- AddForeignKey -ALTER TABLE `build` ADD CONSTRAINT `fk_build_job_id` FOREIGN KEY (`job_id`) REFERENCES `job`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; +ALTER TABLE "public"."build" ADD CONSTRAINT "fk_build_job_id" FOREIGN KEY ("job_id") REFERENCES "public"."job"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; -- AddForeignKey -ALTER TABLE `job` ADD CONSTRAINT `fk_job_client_id` FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; +ALTER TABLE "public"."job" ADD CONSTRAINT "fk_job_client_id" FOREIGN KEY ("client_id") REFERENCES "public"."client"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; -- AddForeignKey -ALTER TABLE `project` ADD CONSTRAINT `fk_project_client_id` FOREIGN KEY (`client_id`) REFERENCES `client`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; +ALTER TABLE "public"."project" ADD CONSTRAINT "fk_project_client_id" FOREIGN KEY ("client_id") REFERENCES "public"."client"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; -- AddForeignKey -ALTER TABLE `release` ADD CONSTRAINT `fk_release_build_id` FOREIGN KEY (`build_id`) REFERENCES `build`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION; - +ALTER TABLE "public"."release" ADD CONSTRAINT "fk_release_build_id" FOREIGN KEY ("build_id") REFERENCES "public"."build"("id") ON DELETE NO ACTION ON UPDATE NO ACTION; diff --git a/src/lib/prisma/migrations/01_prisma_autodate/migration.sql b/src/lib/prisma/migrations/01_prisma_autodate/migration.sql deleted file mode 100644 index a76340a8..00000000 --- a/src/lib/prisma/migrations/01_prisma_autodate/migration.sql +++ /dev/null @@ -1,17 +0,0 @@ --- AlterTable -ALTER TABLE `client` MODIFY `created` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP(0); - --- AlterTable -ALTER TABLE `email_queue` MODIFY `created` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP(0); - --- AlterTable -ALTER TABLE `job` MODIFY `created` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP(0); - --- AlterTable -ALTER TABLE `operation_queue` MODIFY `created` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP(0); - --- AlterTable -ALTER TABLE `project` MODIFY `created` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP(0); - --- AlterTable -ALTER TABLE `release` MODIFY `created` DATETIME(0) NULL DEFAULT CURRENT_TIMESTAMP(0); diff --git a/src/lib/prisma/migrations/migration_lock.toml b/src/lib/prisma/migrations/migration_lock.toml index 592fc0b3..044d57cd 100644 --- a/src/lib/prisma/migrations/migration_lock.toml +++ b/src/lib/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (e.g., Git) -provider = "mysql" +provider = "postgresql" diff --git a/src/lib/prisma/schema.prisma b/src/lib/prisma/schema.prisma index 7c44f5c9..a59db090 100644 --- a/src/lib/prisma/schema.prisma +++ b/src/lib/prisma/schema.prisma @@ -3,7 +3,7 @@ generator client { } datasource db { - provider = "mysql" + provider = "postgresql" url = env("DATABASE_URL") } @@ -13,8 +13,8 @@ model build { status String? @db.VarChar(255) result String? @db.VarChar(255) error String? @db.VarChar(2083) - created DateTime? @db.DateTime(0) - updated DateTime? @db.DateTime(0) + created DateTime? @db.Timestamp(6) + updated DateTime? @db.Timestamp(6) channel String? @db.VarChar(255) version_code Int? artifact_url_base String? @db.VarChar(2083) @@ -23,47 +23,33 @@ model build { console_text_url String? @db.VarChar(255) codebuild_url String? @db.VarChar(255) targets String? @db.VarChar(255) - environment String? @db.Text + environment String? job job @relation(fields: [job_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_build_job_id") release release[] - @@index([job_id], map: "fk_build_job_id") + @@index([job_id], map: "idx_build_job_id") } model client { id Int @id @default(autoincrement()) access_token String @db.VarChar(255) prefix String @db.VarChar(4) - created DateTime? @default(now()) @db.DateTime(0) - updated DateTime? @updatedAt @db.DateTime(0) + created DateTime? @default(now()) @db.Timestamp(6) + updated DateTime? @updatedAt @db.Timestamp(6) job job[] project project[] @@index([access_token], map: "idx_accesS_token") } -model email_queue { - id Int @id @default(autoincrement()) - to String @db.VarChar(255) - cc String? @db.VarChar(255) - bcc String? @db.VarChar(255) - subject String @db.VarChar(255) - text_body String? @db.Text - html_body String? @db.Text - attempts_count Boolean? - last_attempt DateTime? @db.DateTime(0) - created DateTime? @default(now()) @db.DateTime(0) - error String? @db.VarChar(255) -} - model job { id Int @id @default(autoincrement()) request_id String @db.VarChar(255) git_url String @db.VarChar(2083) app_id String @db.VarChar(255) publisher_id String @db.VarChar(255) - created DateTime? @default(now()) @db.DateTime(0) - updated DateTime? @updatedAt @db.DateTime(0) + created DateTime? @default(now()) @db.Timestamp(6) + updated DateTime? @updatedAt @db.Timestamp(6) client_id Int? existing_version_code Int? @default(0) jenkins_build_url String? @db.VarChar(1024) @@ -71,32 +57,10 @@ model job { build build[] client client? @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_job_client_id") - @@index([client_id], map: "fk_job_client_id") + @@index([client_id], map: "idx_job_client_id") @@index([request_id], map: "idx_request_id") } -model migration { - version String @id @db.VarChar(180) - apply_time Int? -} - -model operation_queue { - id Int @id @default(autoincrement()) - operation String @db.VarChar(255) - operation_object_id Int? - operation_parms String? @db.VarChar(2048) - attempt_count Int - last_attempt DateTime? @db.DateTime(0) - try_after DateTime? @db.DateTime(0) - start_time DateTime? @db.DateTime(0) - last_error String? @db.VarChar(2048) - created DateTime? @default(now()) @db.DateTime(0) - updated DateTime? @updatedAt @db.DateTime(0) - - @@index([start_time], map: "idx_start_time") - @@index([try_after], map: "idx_try_after") -} - model project { id Int @id @default(autoincrement()) status String? @db.VarChar(255) @@ -109,20 +73,20 @@ model project { project_name String? @db.VarChar(255) language_code String? @db.VarChar(255) publishing_key String? @db.VarChar(1024) - created DateTime? @default(now()) @db.DateTime(0) - updated DateTime? @updatedAt @db.DateTime(0) + created DateTime? @default(now()) @db.Timestamp(6) + updated DateTime? @updatedAt @db.Timestamp(6) client_id Int? client client? @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_project_client_id") - @@index([client_id], map: "fk_project_client_id") + @@index([client_id], map: "idx_project_client_id") } model release { id Int @id @default(autoincrement()) build_id Int status String? @db.VarChar(255) - created DateTime? @default(now()) @db.DateTime(0) - updated DateTime? @updatedAt @db.DateTime(0) + created DateTime? @default(now()) @db.Timestamp(6) + updated DateTime? @updatedAt @db.Timestamp(6) result String? @db.VarChar(255) error String? @db.VarChar(2083) channel String @db.VarChar(255) @@ -133,10 +97,10 @@ model release { console_text_url String? @db.VarChar(255) codebuild_url String? @db.VarChar(255) targets String? @db.VarChar(255) - environment String? @db.Text + environment String? artifact_url_base String? @db.VarChar(255) artifact_files String? @db.VarChar(255) build build @relation(fields: [build_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_release_build_id") - @@index([build_id], map: "fk_release_build_id") + @@index([build_id], map: "idx_release_build_id") } From b847ebe6d388abeca0f8347735f682b656674d8b Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 16:24:29 -0500 Subject: [PATCH 086/144] Create system startup job to create CodeBuild project --- package-lock.json | 64 +- package.json | 3 +- scripts/project_default/default/default.zip | Bin 0 -> 284 bytes scripts/upload/default/build.sh | 665 ++++++++++++++++++++ scripts/upload/default/publish.sh | 500 +++++++++++++++ src/lib/server/aws/codebuild.ts | 4 +- src/lib/server/aws/s3.ts | 6 + src/lib/server/bullmq/BullMQ.ts | 3 +- src/lib/server/bullmq/BullWorker.ts | 33 +- src/lib/server/bullmq/queues.ts | 7 +- src/lib/server/bullmq/types.ts | 15 +- src/lib/server/job-executors/index.ts | 1 + src/lib/server/job-executors/system.ts | 107 ++++ 13 files changed, 1398 insertions(+), 10 deletions(-) create mode 100644 scripts/project_default/default/default.zip create mode 100644 scripts/upload/default/build.sh create mode 100644 scripts/upload/default/publish.sh create mode 100644 src/lib/server/job-executors/system.ts diff --git a/package-lock.json b/package-lock.json index bd208064..057e9239 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "@aws-sdk/client-s3": "^3.907.0", "@aws-sdk/client-sts": "^3.907.0", "@prisma/client": "^6.15.0", - "prisma": "^6.15.0" + "prisma": "^6.15.0", + "s3-sync-client": "^4.3.1" }, "devDependencies": { "@bull-board/api": "^6.13.1", @@ -314,6 +315,54 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@aws-sdk/abort-controller": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.370.0.tgz", + "integrity": "sha512-/W4arzC/+yVW/cvEXbuwvG0uly4yFSZnnIA+gkqgAm+0HVfacwcPpNf4BjyxjnvIdh03l7w2DriF6MlKUfiQ3A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/types": "3.370.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/abort-controller/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/abort-controller/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/abort-controller/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, "node_modules/@aws-sdk/client-codebuild": { "version": "3.907.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-codebuild/-/client-codebuild-3.907.0.tgz", @@ -9677,6 +9726,19 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/s3-sync-client": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/s3-sync-client/-/s3-sync-client-4.3.1.tgz", + "integrity": "sha512-nWbbKCNnXmWvD8XwdWhX25VNxIhgQEm6vXqSYjwyBNZI07OuMOr/LNOYmEPcLfqFFjy55ZNcFSBI18W29ybuUw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/abort-controller": "^3.x.x", + "@aws-sdk/client-s3": "^3.x.x" + } + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", diff --git a/package.json b/package.json index 1d781948..23482113 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@aws-sdk/client-s3": "^3.907.0", "@aws-sdk/client-sts": "^3.907.0", "@prisma/client": "^6.15.0", - "prisma": "^6.15.0" + "prisma": "^6.15.0", + "s3-sync-client": "^4.3.1" } } diff --git a/scripts/project_default/default/default.zip b/scripts/project_default/default/default.zip new file mode 100644 index 0000000000000000000000000000000000000000..9b87ecbca2c038438ff81d77220d1ced2832c3b7 GIT binary patch literal 284 zcmWIWW@h1H0D(zid46C9l;B_xU`R<#ODxSP(GQK_VK|-B6|*_3E9M8I2txo)UBXDZ zpt|)+DoQ{GfHWfvU}O?y#$_=N%pDGI9YIWlGdSSR2=K;jf-nQZge8q;NG6~-i39GW T0B=?{kVQ;D*bk)3K^z7Ebd)&= literal 0 HcmV?d00001 diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh new file mode 100644 index 00000000..a3d225b0 --- /dev/null +++ b/scripts/upload/default/build.sh @@ -0,0 +1,665 @@ +#!/usr/bin/env bash +set -e -o pipefail +#set -x + +LOG_FILE="${OUTPUT_DIR}"/console.log +exec > >(tee "${LOG_FILE}") 2>&1 + +export PATH="$HOME/.rbenv/bin:$PATH" +eval "$(rbenv init -)" + +BUILD_DIR=/tmp/build +mkdir -p "$BUILD_DIR" +SCRIPT_OPT="-fp build=${BUILD_DIR}" + +sync_secrets() { + SECRETS_SUBDIR=$1 + SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build" + echo "sync secrets" + echo "SECRETS_SUBDIR = ${SECRETS_SUBDIR}" + aws s3 sync "${SECRETS_S3}/${SECRETS_SUBDIR}" "${SECRETS_DIR}" +} + +check_audio_sources() { + if [[ "${BUILD_AUDIO_UPDATE}" == "1" ]]; then + if [[ "${AUDIO_UPDATE_SOURCE}" != "" ]]; then + ADD_AUDIO_UPDATE_SOURCE=1 + SOURCES="$(xmllint --xpath "/app-definition/audio-sources/audio-source/name" "${PROJECT_DIR}/build.appDef")" + IFS='=' read -ra UPDATE_SOURCES <<< "$AUDIO_UPDATE_SOURCE" + for i in "${UPDATE_SOURCES[@]}"; do + if [[ "$SOURCES" != *"${i}"* ]]; then + ADD_AUDIO_UPDATE_SOURCE=0 + fi + done + fi + fi +} + +replace_audio_sources() { + SRC_UPDATE_SOURCE="${UPDATE_SOURCES[0]}" + DST_UPDATE_SOURCE="${UPDATE_SOURCES[1]}" + xmlstarlet ed -u "/app-definition/audio-sources/audio-source/name[text() = '${SRC_UPDATE_SOURCE}']" -v "SCRIPTORIA_SRC_SOURCE" "${PROJECT_DIR}/build.appDef" > "${PROJECT_DIR}/tmp.appDef" + xmlstarlet ed -u "/app-definition/audio-sources/audio-source/name[text() = '${DST_UPDATE_SOURCE}']" -v "SCRIPTORIA_DST_SOURCE" "${PROJECT_DIR}/tmp.appDef" > "${PROJECT_DIR}/build.appDef" + if [[ "${AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE}" == "${SRC_UPDATE_SOURCE}" ]]; then + export AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE="SCRIPTORIA_SRC_SOURCE" + fi + rm "${PROJECT_DIR}/tmp.appDef" +} + +process_audio_sources() { + check_audio_sources + if [[ "${ADD_AUDIO_UPDATE_SOURCE}" == "1" ]]; then + replace_audio_sources + SCRIPT_OPT="${SCRIPT_OPT} -audio-update-source SCRIPTORIA_SRC_SOURCE=SCRIPTORIA_DST_SOURCE" + fi +} + +process_audio_download() { + if [[ "${BUILD_AUDIO_DOWNLOAD}" == "1" ]]; then + if [[ "${AUDIO_DOWNLOAD_MISSING_ASSETS_KEY}" != "" ]]; then + if [[ "${BUILD_AUDIO_DOWNLOAD_URL}" == "" ]]; then + BUILD_AUDIO_DOWNLOAD_URL="https://4.dbt.io" + fi + SCRIPT_OPT="${SCRIPT_OPT} -audio-download-missing-assets-key ${AUDIO_DOWNLOAD_MISSING_ASSETS_KEY} -audio-download-url ${BUILD_AUDIO_DOWNLOAD_URL}" + if [[ "${AUDIO_DOWNLOAD_BITRATE}" != "" ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -audio-download-bitrate ${AUDIO_DOWNLOAD_BITRATE}" + fi + elif [[ "${AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE}" != "" ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -audio-download-missing-assets-source ${AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE}" + fi + if [[ "${AUDIO_DOWNLOAD_CODEC}" != "" ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -audio-download-codec ${AUDIO_DOWNLOAD_CODEC}" + fi + fi +} + +build_apk() { + echo "Build APK" + cd "$PROJECT_DIR" || exit 1 + if [[ "${BUILD_MANAGE_VERSION_CODE}" != "0" ]]; then + VERSION_CODE=$((VERSION_CODE + 1)) + fi + + if [[ "${BUILD_COMPUTE_TYPE}" != "" ]]; then + if [[ "${BUILD_COMPUTE_TYPE}" != "small" ]]; then + export _JAVA_OPTIONS="-Xmx3072M" + fi + fi + if [[ "${BUILD_JAVA_MAX_HEAP_SIZE}" == "1" ]]; then + export _JAVA_OPTIONS="-Xmx2048M" + fi + + echo "BUILD_SHARE_APP_LINK=${BUILD_SHARE_APP_LINK}" + if [[ "${BUILD_SHARE_APP_LINK}" != "0" ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -ft share-app-link=true" + fi + echo "BUILD_SHARE_APP_INSTALLER=${BUILD_SHARE_APP_INSTALLER}" + if [[ "${BUILD_SHARE_APP_INSTALLER}" == "1" ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -ft share-apk-file=true" + fi + echo "BUILD_SHARE_DOWNLOAD_APP_LINK=${BUILD_SHARE_DOWNLOAD_APP_LINK}" + if [[ "${BUILD_SHARE_DOWNLOAD_APP_LINK}" == "1" ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -ft share-download-app-link=true -ft share-download-app-link-url=https://app.scriptoria.io/downloads/apk/${APPDEF_PACKAGE_NAME}/published" + fi + + # if building APK for Google Play, then include data safety CSV in output + echo "APPBUILDER_SCRIPT_VERSION=${APPBUILDER_SCRIPT_VERSION}" + if dpkg --compare-versions "$APPBUILDER_SCRIPT_VERSION" ge "10.3"; then + if [[ "${TARGETS}" == *"play-listing"* ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -data-safety-csv" + fi + fi + + process_audio_sources + process_audio_download + + echo "BUILD_NUMBER=${BUILD_NUMBER}" + echo "VERSION_NAME=${VERSION_NAME}" + echo "VERSION_CODE=${VERSION_CODE}" + echo "OUTPUT_DIR=${OUTPUT_DIR}" + echo "SCRIPT_OPT=${SCRIPT_OPT}" + + if [[ "${BUILD_KEYSTORE}" != "" ]]; then + echo "Using build keystore=${BUILD_KEYSTORE}" + SECRETS_SUBDIR="google_play_store/${PUBLISHER}/${BUILD_KEYSTORE}" + sync_secrets "${SECRETS_SUBDIR}" + KS="${SECRETS_DIR}/${BUILD_KEYSTORE}.keystore" + else + echo "Using publisher keystore=${PUBLISHER}" + SECRETS_SUBDIR="google_play_store/${PUBLISHER}" + sync_secrets "${SECRETS_SUBDIR}" + KS="${SECRETS_DIR}/${PUBLISHER}.keystore" + fi + KSP="$(cat "${SECRETS_DIR}/ksp.txt")" + KA="$(cat "${SECRETS_DIR}/ka.txt")" + KAP="$(cat "${SECRETS_DIR}/kap.txt")" + { echo "-ksp \"${KSP}\"" ; echo "-ka \"${KA}\""; echo "-kap \"${KAP}\""; } >> "${SECRETS_DIR}/keys.txt" + KS_OPT="-ks ${KS} -i ${SECRETS_DIR}/keys.txt" + + echo "KEYSTORE=${KS}" + + cd "$PROJECT_DIR" || exit 1 + + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} + if [[ "${BUILD_ANDROID_AAB}" == "1" ]]; then + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -app-bundle ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} + fi + + # verify output -- AAPT2 is failing during appbuilder build but error is not getting back to script + pushd "$OUTPUT_DIR" + shopt -s nullglob + for f in *.{apk,aab}; do + echo "JARSIGNER: Checking ${OUTPUT_DIR}/$f" + jarsigner -verify "$f" + done + shopt -u nullglob + popd + + ### + # For the download page, we need the primary color and localized string for "Download APK" + # + # Add primary-color. Look for an overriden value (will return error if not found). If not found, then look in build values (yuck). + set +e + PRIMARY_COLOR=$(xmlstarlet sel -t -v '/app-definition/colors/color[@name = "PrimaryColor"]/color-mapping/@value' "${PROJECT_DIR}/build.appDef" ) || echo "Color not set in appDef" + if [[ "${PRIMARY_COLOR}" == "" ]]; then + # This is a little ugly getting the color after a build + PRIMARY_COLOR=$(xmlstarlet sel -t -v '/resources/color[@name = "colorPrimary"]' "/tmp/App Builder/build/${APP_BUILDER_TLA}.000/a/res/values/colors.xml") + fi + echo "$PRIMARY_COLOR" > "${PROJECT_DIR}/build_data/publish/play-listing/primary-color.txt" + + # Add download-apk-strings.json + # Extract the entries from appDef as XML and then convert to JSON. + # It is possible to have empty entries (e.g. ) which xmlstarlet returns as . + # This requires the first regex to remove these or it will cause problems with the converstion to JSON + DOWNLOAD_STRINGS=$(xmlstarlet sel -t -c '/app-definition/translation-mappings/translation-mapping[@id = "Download_APK"]/translation' "${PROJECT_DIR}/build.appDef" | sed -r 's///g; s//" ,/g; s/>/ : "/g' | sed 's/^/{/; s/,$/}/') + if [[ "${DOWNLOAD_STRINGS}" == "" ]]; then + DOWNLOAD_STRINGS='{"en" : "Download APK"}' + fi + echo "$DOWNLOAD_STRINGS" > "${PROJECT_DIR}/build_data/publish/play-listing/download-apk-strings.json" + set -e +} + +build_html() { + echo "Build html" + echo "OUTPUT_DIR=${OUTPUT_DIR}" + cd "$PROJECT_DIR" || exit 1 + + HTML_OUTPUT_DIR=/tmp/output/html + mkdir -p "${HTML_OUTPUT_DIR}" + if [[ "${BUILD_HTML_COLLECTION_ID}" == *","* ]]; then + IFS=',' read -ra COLLECTIONS <<< "${BUILD_HTML_COLLECTION_ID}" + for i in "${COLLECTIONS[@]}"; do + IFS='=' read -ra PARAMS <<< "${i}" + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -html "${PARAMS[0]}" -p "${PARAMS[1]}" -fp html.output="${HTML_OUTPUT_DIR}" ${SCRIPT_OPT} + done + pushd "${HTML_OUTPUT_DIR}" + else + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -html "${BUILD_HTML_COLLECTION_ID}" -fp html.output="${HTML_OUTPUT_DIR}" ${SCRIPT_OPT} + pushd "${HTML_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}" + fi + zip -r "${OUTPUT_DIR}/html.zip" . + popd + # Not exported so clear it + VERSION_CODE="" + APPDEF_PACKAGE_NAME="" +} + +make_pwa_audio_url_relative() { + AUDIO_URL=$(xmlstarlet sel -t -v "/app-definition/features/feature[@name = 'export-html-audio-path']/@value" "${PROJECT_DIR}/build.appDef" || true) + if [[ "${AUDIO_URL}" == "" ]]; then + echo "Export HTML Audio Path not set" + exit 1 + fi + AUDIO_RELATIVE_URL=$(echo "${AUDIO_URL}" | sed -E 's/^https?://') + xmlstarlet ed --inplace -u "/app-definition/features/feature[@name = 'export-html-audio-path']/@value" -v "${AUDIO_RELATIVE_URL}" "${PROJECT_DIR}/build.appDef" +} + +build_pwa() { + echo "Build pwa" + echo "OUTPUT_DIR=${OUTPUT_DIR}" + cd "$PROJECT_DIR" || exit 1 + + if [[ "${BUILD_PWA_AUDIO_RELATIVE_URL}" == "1" ]]; then + make_pwa_audio_url_relative + fi + PWA_OUTPUT_DIR=/tmp/output/pwa + mkdir -p "${PWA_OUTPUT_DIR}" + if [[ "${BUILD_PWA_COLLECTION_ID}" == *","* ]]; then + IFS=',' read -ra COLLECTIONS <<< "${BUILD_PWA_COLLECTION_ID}" + for i in "${COLLECTIONS[@]}"; do + IFS='=' read -ra PARAMS <<< "${i}" + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -pwa "${PARAMS[0]}" -p "${PARAMS[1]}" -fp html.output="${PWA_OUTPUT_DIR}" ${SCRIPT_OPT} + done + pushd "${PWA_OUTPUT_DIR}" + else + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -pwa "${BUILD_PWA_COLLECTION_ID}" -fp html.output="${PWA_OUTPUT_DIR}" ${SCRIPT_OPT} + pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}" + fi + zip -r "${OUTPUT_DIR}/pwa.zip" . + popd + # Not exported so clear it + VERSION_CODE="" + APPDEF_PACKAGE_NAME="" +} + +build_modern_pwa() { + echo "Build Modern PWA" + echo "OUTPUT_DIR=${OUTPUT_DIR}" + cd "$PROJECT_DIR" || exit 1 + + PWA_OUTPUT_DIR=/tmp/output/pwa + mkdir -p "${PWA_OUTPUT_DIR}" + + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build-modern-pwa -fp pwa.output="${PWA_OUTPUT_DIR}" ${SCRIPT_OPT} + pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}/build" + zip -r "${OUTPUT_DIR}/pwa.zip" . + popd + VERSION_CODE="" + APPDEF_PACKAGE_NAME="" +} + +set_default_asset_package() { + ASSET_FILENAME="${APPDEF_PACKAGE_NAME}.zip" + echo "Updating ipa-app-type=assets" + echo "Updating ipa-asset-filename=${ASSET_FILENAME}" + echo "Project=${PROJECT_DIR}/build.appDef" + if grep -q "" "${PROJECT_DIR}/build.appDef"; then + xmlstarlet ed --inplace -u "/app-definition/ipa-app-type" -v "assets" "${PROJECT_DIR}/build.appDef" + else + xmlstarlet ed --inplace -s "/app-definition" -t elem -n "ipa-app-type" -v "assets" "${PROJECT_DIR}/build.appDef" + fi + if grep -q "" "${PROJECT_DIR}/build.appDef"; then + xmlstarlet ed --inplace -u "/app-definition/ipa-asset-filename" -v "${ASSET_FILENAME}" "${PROJECT_DIR}/build.appDef" + else + xmlstarlet ed --inplace -s "/app-definition" -t elem -n "ipa-asset-filename" -v "${ASSET_FILENAME}" "${PROJECT_DIR}/build.appDef" + fi +} + +build_asset_package() { + echo "Build asset-package" + echo "OUTPUT_DIR=${OUTPUT_DIR}" + cd "$PROJECT_DIR" || exit 1 + + ASSET_OUTPUT_DIR="${OUTPUT_DIR}/asset-package" + mkdir -p "${ASSET_OUTPUT_DIR}" + + APP_TYPE_COUNT=$(xmlstarlet sel -t -v "count(/app-definition/ipa-app-type)" "${PROJECT_DIR}/build.appDef") + ASSET_FILENAME_COUNT=$(xmlstarlet sel -t -v "count(/app-definition/ipa-asset-filename)" "${PROJECT_DIR}/build.appDef") + if [[ "$APP_TYPE_COUNT" == 0 || "$ASSET_FILENAME_COUNT" == 0 ]]; then + # Older project; provide default + set_default_asset_package + else + APP_TYPE=$(xmlstarlet sel -t -v "/app-definition/ipa-app-type" "${PROJECT_DIR}/build.appDef") + if [[ "$APP_TYPE" != "assets" ]]; then + set_default_asset_package + fi + fi + + APP_TYPE=$(xmlstarlet sel -t -v "/app-definition/ipa-app-type" "${PROJECT_DIR}/build.appDef") + ASSET_FILENAME="$(xmlstarlet sel -t -v "//app-definition/ipa-asset-filename" "${PROJECT_DIR}/build.appDef")" + if [[ "$ASSET_FILENAME" == "" ]]; then + set_default_asset_package + fi + APP_NAME="$(xmlstarlet sel -t -v "/app-definition/app-name" "${PROJECT_DIR}/build.appDef")" + echo "APP_TYPE=${APP_TYPE}" + echo "ASSET_FILENAME=${ASSET_FILENAME}" + echo "APP_NAME=${APP_NAME}" + + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build-assets -fp ipa.output="${ASSET_OUTPUT_DIR}" -vn "$VERSION_NAME" ${SCRIPT_OPT} + + # Build preview + cat >"${ASSET_OUTPUT_DIR}/preview.html" < + +

    Preview

    + + + +EOL + + ### Build notification + + # query langtags + NOTIFY_LANG_TMP=$(mktemp) + jq -cM "{app_lang: .[] | select(.tag==\"${PROJECT_LANGUAGE}\") }" /root/langtags.json > "${NOTIFY_LANG_TMP}" + if [ ! -s "${NOTIFY_LANG_TMP}" ]; then + # The language was not found; provide default + echo '{}' | jq -cM --arg lang "${PROJECT_LANGUAGE}" '. + { app_lang: { tag: $lang } }' > "$NOTIFY_LANG_TMP" + fi + + # build listing + NOTIFY_LISTING_TMP=$(mktemp) + NOTIFY_BASE_TMP=$(mktemp) + PLAY_LISTING_DIR="build_data/publish/play-listing" + echo '{}' | jq -cM '. + { listing: [] }' > "$NOTIFY_BASE_TMP" + pushd "${PLAY_LISTING_DIR}" + NOTIFY_LANGS=$(find . -mindepth 1 -maxdepth 1 -type d | cut -d/ -f2) + for lang in $NOTIFY_LANGS + do + sd="$(cat "$lang/short_description.txt")" + fd="$(cat "$lang/full_description.txt")" + title="$(cat "$lang/title.txt")" + jq -cM --arg lang "$lang" --arg sd "$sd" --arg fd "$fd" --arg title "$title" \ + '.listing += [ { lang: $lang, title: $title, short_description: $sd, full_description: $fd } ]' \ + "$NOTIFY_BASE_TMP" > "$NOTIFY_LISTING_TMP" + cp "$NOTIFY_LISTING_TMP" "$NOTIFY_BASE_TMP" + done + popd + + # extract image + pushd "build_data/images" + # look for ios images + NOTIFY_IMAGES_TMP=$(mktemp) + echo '{}' | jq -cM '. + { image: { files: [] } }' > "$NOTIFY_BASE_TMP" + if [ -d "ios/drawer" ]; then + # copy predefined images + pushd "ios/drawer" + NOTIFY_IMAGES=$(find . -mindepth 1 -maxdepth 1 -type f | cut -d/ -f2) + for image in $NOTIFY_IMAGES + do + size=1x + if [[ "$image" == *"@2x"* ]]; then + size=2x + elif [[ "$image" == *"@3x"* ]]; then + size=3x + fi + jq -cM --arg size "$size" --arg image "$image" '.image.files += [ {size: $size, src: $image} ]' "$NOTIFY_BASE_TMP" > "$NOTIFY_IMAGES_TMP" + cp "$image" "${ASSET_OUTPUT_DIR}/${image}" + cp "$NOTIFY_IMAGES_TMP" "$NOTIFY_BASE_TMP" + done + popd + else + # resize largest image to desired sizes + for size in "xxxhdpi" "xxhdpi" "xhdpi" "hdpi" + do + image_file="drawable-${size}/nav_drawer.png" + if [ -f "${image_file}" ]; then + convert "${image_file}" -resize 750x422 "${ASSET_OUTPUT_DIR}/nav_drawer@3x.png" + convert "${image_file}" -resize 500x282 "${ASSET_OUTPUT_DIR}/nav_drawer@2x.png" + convert "${image_file}" -resize 250x141 "${ASSET_OUTPUT_DIR}/nav_drawer.png" + jq -cM '.image.files += [ { size: "1x", src: "nav_drawer.png" }, { size: "2x", src: "nav_drawer@2x.png" }, {size: "3x", src: "nav_drawer@3x.png" } ]' "$NOTIFY_BASE_TMP" > "$NOTIFY_IMAGES_TMP" + break + fi + done + fi + popd + + # combine json objects + if [ -s "${NOTIFY_IMAGES_TMP}" ]; then + jq -cM -s '.[0] * .[1] * .[2]' "$NOTIFY_LANG_TMP" "$NOTIFY_IMAGES_TMP" "$NOTIFY_LISTING_TMP" > "${ASSET_OUTPUT_DIR}/notify.json" + else + jq -cM -s '.[0] * .[1]' "$NOTIFY_LANG_TMP" "$NOTIFY_LISTING_TMP" > "${ASSET_OUTPUT_DIR}/notify.json" + fi + + # Not exported so clear it + VERSION_CODE="" +} + +build_play_listing() { + echo "Build play listing" + echo "BUILD_NUMBER=${BUILD_NUMBER}" + echo "VERSION_NAME=${VERSION_NAME}" + echo "VERSION_CODE=${VERSION_CODE}" + echo "OUTPUT_DIR=${OUTPUT_DIR}" + cd "$PROJECT_DIR" || exit 1 + + APK_FILES=("${OUTPUT_DIR}"/*.apk) + AAPT="$(find /opt/android-sdk/build-tools -name aapt | head -n 1)" + + if [ -f "build_data/about/about.txt" ]; then + cp build_data/about/about.txt "$OUTPUT_DIR"/ + fi + PUBLISH_DIR="build_data/publish" + PLAY_LISTING_DIR="${PUBLISH_DIR}/play-listing" + LIST_DIR="${PLAY_LISTING_DIR}/" + MANIFEST_FILE="manifest.txt" + if [ -f $LIST_DIR$MANIFEST_FILE ]; then + rm $LIST_DIR$MANIFEST_FILE + fi + find $PLAY_LISTING_DIR -type f | while read -r f + do + fn=${f#*"$PLAY_LISTING_DIR/"} + echo "$fn" >> "$OUTPUT_DIR"/$MANIFEST_FILE + done + if [ -d "$PLAY_LISTING_DIR" ]; then + cp -r "$PLAY_LISTING_DIR" "$OUTPUT_DIR" + find "$OUTPUT_DIR" -name whats_new.txt | while read -r filename + do + DIR=$(dirname "${filename}") + cp "$filename" "$OUTPUT_DIR" + mkdir "${DIR}/changelogs" + if [ "${#APK_FILES[@]}" -gt 1 ]; then + # If there are multiple APK files, we only need to provide 1 changelog, + # but it has to match one of the version codes. + apk=${APK_FILES[0]} + APK_VERSION_CODE=$($AAPT dump badging "${apk}" | grep "^package" | sed -n "s/.*versionCode='\([0-9]*\).*/\1/p") + cp "$filename" "${DIR}/changelogs/${APK_VERSION_CODE}.txt" + else + cp "$filename" "${DIR}/changelogs/${VERSION_CODE}.txt" + fi + done + fi +} + +build_gradle() { + echo "Gradle $1" + if [ -f "${PROJECT_DIR}/build.gradle" ]; then + pushd "$PROJECT_DIR" || exit 1 + gradle "$1" + popd || exit 1 + elif [ -f "${SCRIPT_DIR}/build.gradle" ]; then + pushd "$SCRIPT_DIR" || exit 1 + gradle "$1" + popd || exit 1 + fi +} + +prepare_appbuilder_dir() { + # Ensure 'App Projects' directory + if [[ "${APP_BUILDER_SCRIPT_PATH}" == "scripture-app-builder" ]]; then + APP_BUILDER_TLA=SAB + APP_BUILDER_FOLDER="Scripture Apps" + elif [[ "${APP_BUILDER_SCRIPT_PATH}" == "reading-app-builder" ]]; then + APP_BUILDER_TLA=RAB + APP_BUILDER_FOLDER="Reading Apps" + elif [[ "${APP_BUILDER_SCRIPT_PATH}" == "dictionary-app-builder" ]]; then + APP_BUILDER_TLA=DAB + APP_BUILDER_FOLDER="Dictionary Apps" + elif [[ "${APP_BUILDER_SCRIPT_PATH}" == "keyboard-app-builder" ]]; then + APP_BUILDER_TLA=KAB + APP_BUILDER_FOLDER="Keyboard Apps" + fi + + mkdir -p "${HOME}/App Builder/${APP_BUILDER_FOLDER}/App Projects" + + if [[ "${BUILD_KEYS_FILE}" != "" ]]; then + KEY_DEST_DIR="${HOME}/App Builder/${APP_BUILDER_FOLDER}" + mkdir -p "${KEY_DEST_DIR}" + cp "${PROJECT_DIR}/build_data/${BUILD_KEYS_FILE}" "${KEY_DEST_DIR}/keys.txt" + fi +} + +prepare_appbuilder_project() { + # In the past, we have had problems with multiple .appDef files being checked in and confusing error. + # Fail quickly in this situation + cd "$PROJECT_DIR" || exit 1 + PROJ_COUNT=$(find . -maxdepth 1 -name "*.appDef" | wc -l) + if [[ "$PROJ_COUNT" -ne "1" ]]; then + echo "ERROR: Wrong number of projects: ${PROJ_COUNT}" + exit 2 + fi + + APPBUILDER_SCRIPT_VERSION=$($APP_BUILDER_SCRIPT_PATH -? | grep "Version" | cut -d\ -f2) + + PROJ_NAME=$(basename -- *.appDef .appDef) + PROJ_DIR=$(find . -maxdepth 1 -type d | grep -i -F "${PROJ_NAME}_data") + if [[ -f "${PROJ_NAME}.appDef" && -d "${PROJ_DIR}" ]]; then + echo "Moving ${PROJ_NAME}.appDef and ${PROJ_DIR}" + mv "${PROJ_NAME}.appDef" build.appDef + mv "${PROJ_DIR}" build_data + else + echo "ERROR: Project appDef or project data not found" + exit 3 + fi + + PUBLISH_PROPERTIES="build_data/publish/properties.json" + if [ -f "${PUBLISH_PROPERTIES}" ]; then + # Handle spaces in properties values + # https://stackoverflow.com/a/48513046/35577 + values=$(cat "${PUBLISH_PROPERTIES}") + while read -rd $'' line + do + echo "exporting ${line}" + export "${line?}" + done < <(jq -r <<<"$values" 'to_entries|map("\(.key)=\(.value)\u0000")[]') + + OUTPUT_PUBLISH_PROPERTIES="${OUTPUT_DIR}/publish-properties.json" + # Add addition properties + PUBLISH_SE_RECORD="build_data/publish/se-record.json" + if [[ -f "${PUBLISH_SE_RECORD}" && "$(jq -r '. | length' "${PUBLISH_SE_RECORD}")" == "1" ]]; then + # if there is at least one Scripture Earth record + # Note: it is possible to have the record, but not the notify property -- "|| true" eats the error and still returns blank + PUBLISH_NOTIFY_SCRIPTURE_EARTH=$(xmlstarlet sel -t -v "/app-definition/publishing/scripture-earth/@notify" build.appDef || true) + PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID=$(jq -r '.["0"].relationships.idx' "${PUBLISH_SE_RECORD}") + if [[ "${PUBLISH_NOTIFY_SCRIPTURE_EARTH}" == "true" ]]; then + echo "Notify Scripture Earth: id=${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID}" + # If the "Notify Scripture Earth" property is enabled in the AppDef + PUBLISH_TMP=$(mktemp) + PUBLISH_NOTIFY_TYPE=$(jq -r '.PUBLISH_NOTIFY | type' "${PUBLISH_PROPERTIES}") + # We are currently only supporting notifying Scripture Earth of product updates. + # However, Kalaam has expressed interested in being notified as well. This would + # allow Kalaam to add a PUBLISH_NOTIFY publishing property. We would also have to + # implement something in publish.sh to notify their server correctly. + if [[ "${PUBLISH_NOTIFY_TYPE}" == "null" ]]; then + # There is no property so set it (as an array) to Scripture Earth entry + jq -cM '.PUBLISH_NOTIFY += "SCRIPTURE_EARTH"' "${PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" + elif [ "${PUBLISH_NOTIFY_TYPE}" == "string" ]; then + # There is an existing property so convert to an array and add to Scripture Earth entry. + # We are only going to deal with one item being there. If there will be multiple, then we + # will have to split the string and create an array of the results + PUBLISH_NOTIFY_CURRENT=$(jq -r '.PUBLISH_NOTIFY' "${PUBLISH_PROPERTIES}") + jq -cM --arg cur "${PUBLISH_NOTIFY_CURRENT}" '.PUBLISH_NOTIFY += ",SCRIPTURE_EARTH"' "${PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" + fi + cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" + jq -cM --arg idx "${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID}" '.SCRIPTURE_EARTH_ID = $idx' "${OUTPUT_PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" + cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" + fi + fi + + # If modern-pwa, then update the subdirectory configuration from the rclone publish path if not defined + for target in $TARGETS; do + if [ "$target" = "modern-pwa" ]; then + INPUT_PUBLISH_PROPERTIES=$PUBLISH_PROPERTIES + if [ -f "${OUTPUT_PUBLISH_PROPERTIES}" ]; then + INPUT_PUBLISH_PROPERTIES=$OUTPUT_PUBLISH_PROPERTIES + fi + if jq -e '.PUBLISH_CLOUD_REMOTE_PATH' "${INPUT_PUBLISH_PROPERTIES}" >/dev/null; then + if ! xmlstarlet sel -t -v "/app-definition/pwa-manifest/pwa-sub-directory" build.appDef 2>/dev/null; then + # Note: The #/ in the variable expansion removes any leading slashes if it exists. + # This makes sure the string begins with a single slash + PWA_SUBDIR="/${PUBLISH_CLOUD_REMOTE_PATH#/}" + echo "PUBLISH_CLOUD_REMOTE_PATH exists, but PWA Sub Directory is missing." + echo "PUBLISH_CLOUD_REMOTE_PATH=${PUBLISH_CLOUD_REMOTE_PATH} so update PWA Sub Directory=${PWA_SUBDIR}" + APPDEF_TMP=$(mktemp) + xmlstarlet ed \ + -s "/app-definition" -t elem -n "pwa-manifest" -v "" \ + -s "/app-definition/pwa-manifest" -t elem -n "pwa-sub-directory" -v "${PWA_SUBDIR}" \ + build.appDef > "${APPDEF_TMP}" + cp "${APPDEF_TMP}" build.appDef + fi + else + PWA_SUBDIR=$(xmllint --xpath "/app-definition/pwa-manifest/pwa-sub-directory/text()" build.appDef 2>/dev/null || echo "") + if [ "$PWA_SUBDIR" != "" ]; then + echo "PUBLISH_CLOUD_REMOTE_PATH does not exist, but PWA Sub Directory is set." + echo "PWA Sub Directory=${PWA_SUBDIR} so update PUBLISH_CLOUD_REMOTE_PATH=${PWA_SUBDIR#/}" + PUBLISH_TMP=$(mktemp) + jq -cM ".PUBLISH_CLOUD_REMOTE_PATH += \"${PWA_SUBDIR#/}\"" "${INPUT_PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" + cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" + fi + fi + fi + done + + if [ ! -f "${OUTPUT_PUBLISH_PROPERTIES}" ]; then + # if no Scripture Earth record, then copy straight as normal + cp "${PUBLISH_PROPERTIES}" "${OUTPUT_PUBLISH_PROPERTIES}" + fi + + cat "${OUTPUT_PUBLISH_PROPERTIES}" + fi + + APPDEF_VERSION_NAME=$(xmllint --xpath "string(/app-definition/version/@name)" build.appDef) + echo "APPDEF_VERSION_NAME=${APPDEF_VERSION_NAME}" + echo "BUILD_MANAGE_VERSION_NAME=${BUILD_MANAGE_VERSION_NAME}" + if [[ "${BUILD_MANAGE_VERSION_NAME}" == "0" ]]; then + VERSION_NAME=${APPDEF_VERSION_NAME} + else + VERSION_NAME=$("${APP_BUILDER_SCRIPT_PATH}" -? | grep 'Version' | awk -F '[ +]' '{print $2}') + fi + + APPDEF_PACKAGE_NAME=$(xmllint --xpath "/app-definition/package/text()" build.appDef) + echo "APPDEF_PACKAGE_NAME=${APPDEF_PACKAGE_NAME}" + + APPDEF_VERSION_CODE=$(xmllint --xpath "string(/app-definition/version/@code)" build.appDef) + echo "APPDEF_VERSION_CODE=${APPDEF_VERSION_CODE}" + echo "BUILD_MANAGE_VERSION_CODE=${BUILD_MANAGE_VERSION_CODE}" + if [[ "${BUILD_MANAGE_VERSION_CODE}" == "0" ]]; then + VERSION_CODE=$((APPDEF_VERSION_CODE)) + else + if [[ "$APPDEF_VERSION_CODE" -gt "$VERSION_CODE" ]]; then VERSION_CODE=$((APPDEF_VERSION_CODE)); fi + fi +} + +complete_successful_build() { + if [[ "${APPDEF_PACKAGE_NAME}" != "" ]]; then + echo "${APPDEF_PACKAGE_NAME}" > "$OUTPUT_DIR"/package_name.txt + fi + if [[ "${VERSION_CODE}" != "" ]]; then + echo "${VERSION_CODE}" > "$OUTPUT_DIR"/version_code.txt + echo "{ \"version\" : \"${VERSION_NAME} (${VERSION_CODE})\", \"versionName\" : \"${VERSION_NAME}\", \"versionCode\" : \"${VERSION_CODE}\" } " > "$OUTPUT_DIR"/version.json + else + echo "{ \"version\" : \"${VERSION_NAME}\", \"versionName\" : \"${VERSION_NAME}\" } " > "$OUTPUT_DIR"/version.json + fi + + echo "ls -lR ${OUTPUT_DIR}" + ls -lR "${OUTPUT_DIR}" +} + +env | sort +prepare_appbuilder_project +prepare_appbuilder_dir + +echo "TARGETS: $TARGETS" +for target in $TARGETS +do + case "$target" in + "apk") build_apk ;; + "asset-package") build_asset_package ;; + "play-listing") build_play_listing ;; + "html") build_html ;; + "pwa") build_pwa ;; + "modern-pwa") build_modern_pwa ;; + *) build_gradle "$target" ;; + esac +done + +complete_successful_build diff --git a/scripts/upload/default/publish.sh b/scripts/upload/default/publish.sh new file mode 100644 index 00000000..86f5d312 --- /dev/null +++ b/scripts/upload/default/publish.sh @@ -0,0 +1,500 @@ +#!/usr/bin/env bash +shopt -s nullglob +set -e -o pipefail +set -x +LOG_FILE="${OUTPUT_DIR}"/console.log +exec > >(tee "${LOG_FILE}") 2>&1 + +export PATH="$HOME/.rbenv/bin:$PATH" +eval "$(rbenv init -)" +sync_secrets() { + SECRETS_SUBDIR=$1 + SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/publish" + echo "sync secrets" + echo "SECRETS_SUBDIR = ${SECRETS_SUBDIR}" + aws s3 sync "${SECRETS_S3}/${SECRETS_SUBDIR}" "${SECRETS_DIR}" +} + +publish_google_play() { + fastlane env + echo "OUTPUT_DIR=${OUTPUT_DIR}" + SECRETS_SUBDIR="google_play_store/${PUBLISHER}" + sync_secrets "${SECRETS_SUBDIR}" + export SUPPLY_JSON_KEY="${SECRETS_DIR}/playstore_api.json" + cd "$ARTIFACTS_DIR" || exit 1 + PACKAGE_NAME="$(cat package_name.txt)" + export SUPPLY_PACKAGE_NAME="${PACKAGE_NAME}" + VERSION_CODE="$(cat version_code.txt)" + export SUPPLY_METADATA_PATH="play-listing" + + if [[ -n "${PUBLISH_NO_APK}" ]]; then + export SUPPLY_SKIP_UPLOAD_APK=true + export SUPPLY_SKIP_UPLOAD_AAB=true + echo "APK: None (PUBLISH_NO_APK=1), just republish" + elif [[ "${#AAB_FILES[@]}" -gt 0 ]]; then + export SUPPLY_AAB="${AAB_FILES[0]}" + export SUPPLY_SKIP_UPLOAD_APK=true + echo "AAB: ${SUPPLY_AAB}" + elif [[ "${#APK_FILES[@]}" -gt 1 ]]; then + # Build a comma-separated list of files + SUPPLY_APK_PATHS=$(find . -name "*.apk" | tr '\n' ',') + export SUPPLY_APK_PATHS + echo "APKs: ${SUPPLY_APK_PATHS}" + elif [[ "${#APK_FILES[@]}" -gt 0 ]]; then + export SUPPLY_APK="${APK_FILES[0]}" + echo "APK: ${SUPPLY_APK}" + else + echo "APK: Missing AAB or APK!" + exit 1 + fi + + if [[ -n "${PUBLISH_GOOGLE_PLAY_DRAFT}" ]]; then + echo "Publishing Draft" + export SUPPLY_RELEASE_STATUS=draft + # On the initial publish, a user has to create the app store entry and upload the APK + # to associate the entry with the package name and keystore + # Google Play APIs have changed so that we can't re-upload the APK. It gives a duplicate + # version code error. So we need to skip uploading APK that was uploaded by the user + if [[ "${VERSION_CODE}" == "${PUBLISH_GOOGLE_PLAY_UPLOADED_VERSION_CODE}" ]]; then + if [[ "${BUILD_NUMBER}" != "${PUBLISH_GOOGLE_PLAY_UPLOADED_BUILD_ID}" ]]; then + echo "ERROR: Duplicate version code used on different builds during initial publish" + exit 1 + fi + echo "Not publishing APK(s) or AAB ... uploaded by user" + export SUPPLY_SKIP_UPLOAD_APK=true + export SUPPLY_SKIP_UPLOAD_AAB=true + else + echo "Publishing APK(s) ... rebuilt after first uploaded by user" + fi + + fi + + # https://stackoverflow.com/a/23357277/35577 + CHANGELOGS=() + if [[ -z "${PUBLISH_NO_APK}" ]]; then + while IFS= read -r -d $'\0'; do + CHANGELOGS+=("$REPLY") + done < <(find "${ARTIFACTS_DIR}"/play-listing -name "[0-9]*.txt" -print0 | grep -FzZ 'changelogs') + fi + + if [[ "${#CHANGELOGS[@]}" -gt 0 ]]; then + APK_VERSION_CODE="$(basename "${CHANGELOGS[0]%.txt}")" + export SUPPLY_VERSION_CODE="${APK_VERSION_CODE}" + else + export SUPPLY_SKIP_UPLOAD_CHANGELOGS=true + fi + + if [ -n "$PROMOTE_FROM" ]; then + export SUPPLY_TRACK="${PROMOTE_FROM}" + export SUPPLY_TRACK_PROMOTE_TO="${CHANNEL}" + else + export SUPPLY_TRACK="${CHANNEL}" + fi + + # https://github.com/fastlane/fastlane/issues/21507 + # Google Api Error: Unathorized - Request is missing required authentication credential + # Retry to work-around + # if [ -z "${SUPPLY_UPLOAD_MAX_RETRIES}" ]; then + # export SUPPLY_UPLOAD_MAX_RETRIES=5 + # fi + + env | grep "SUPPLY_" + fastlane supply + if [[ -n "${PUBLISH_NO_APK}" ]]; then + PUBLISH_SIZE=0 + PERMALINK_URL="" + else + PUBLISH_SIZE="$(stat --format="%s" "${APK_FILES[0]}")" + PERMALINK_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/apk" + fi + PUBLISH_URL="https://play.google.com/store/apps/details?id=${PACKAGE_NAME}" + echo "${PUBLISH_URL}" > "${OUTPUT_DIR}/publish_url.txt" + echo "ls -l ${OUTPUT_DIR}" + ls -l "${OUTPUT_DIR}" +} + +publish_s3_bucket() { + ZIP_FILES=( "${ARTIFACTS_DIR}"/asset-package/*.zip ) + + SECRETS_SUBDIR="s3_bucket/${PUBLISHER}" + sync_secrets "${SECRETS_SUBDIR}" + CREDENTIALS="${SECRETS_DIR}/credentials" + CONFIG="${SECRETS_DIR}/config" + DEST_BUCKET_PATH=$(cat "${SECRETS_DIR}/bucket") + if [[ "${DEST_BUCKET_PATH}" == "" ]]; then + echo "bucket file for S3 ${PUBLISHER} is empty!" + exit 1 + fi + + # add the product id to make it unique + if [[ "${PRODUCT_ID}" != "" && "${PUBLISH_S3_ROOT}" == "" ]]; then + DEST_BUCKET_PATH="${DEST_BUCKET_PATH}/${PRODUCT_ID}" + fi + + IFS=/ read -r DEST_BUCKET DEST_PATH <<< "${DEST_BUCKET_PATH}" + + # Detect tye type of publish: apk or asset-package + if [[ "${#APK_FILES[@]}" -gt 0 ]]; then + # apk: publish all the apks + PUBLISH_S3_INCLUDE="*.apk" + PUBLISH_S3_SOURCH_PATH="${ARTIFACTS_DIR}" + PERMALINK_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/apk" + SRC_FILE="${APK_FILES[0]}" + elif [[ "${#ZIP_FILES[@]}" -gt 0 ]]; then + # asset-package: publish all the zip files + PUBLISH_S3_INCLUDE="*.zip" + PUBLISH_S3_SOURCH_PATH="${ARTIFACTS_DIR}/asset-package" + PERMALINK_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/asset-package" + SRC_FILE="${ZIP_FILES[0]}" + fi + PUBLISH_SIZE="$(stat --format="%s" "${SRC_FILE}")" + DEST_FILE="$(basename "${SRC_FILE}")" + + if [[ "${DEST_PATH}" == "" ]]; then + PUBLISH_BASE_URL="https://${DEST_BUCKET}.s3.amazonaws.com" + else + PUBLISH_BASE_URL="https://${DEST_BUCKET}.s3.amazonaws.com/${DEST_PATH}" + fi + PUBLISH_URL="${PUBLISH_BASE_URL}/${DEST_FILE}" + + echo "CREDENTIALS=${CREDENTIALS}" + echo "CONFIG=${CONFIG}" + echo "SRC_FILE=${SRC_FILE}" + echo "PUBLISH_S3_SOURCH_PATH=${PUBLISH_S3_SOURCH_PATH}" + echo "PUBLISH_S3_INCLUDE=${PUBLISH_S3_INCLUDE}" + echo "DEST_BUCKET_PATH=${DEST_BUCKET_PATH}" + echo "DEST_FILE=${DEST_FILE}" + echo "PUBLISH_URL=${PUBLISH_URL}" + + NOTIFY_ASSET_BASE_JSON="${ARTIFACTS_DIR}/asset-package/notify.json" + if [ -f "${NOTIFY_ASSET_BASE_JSON}" ]; then + NOTIFY_ASSET_JSON_TMP=$(mktemp) + # set the baseurl for the images + jq -cM --arg baseurl "${PUBLISH_BASE_URL}" '.image += { baseurl: $baseurl }' "${NOTIFY_ASSET_BASE_JSON}" > "${NOTIFY_ASSET_JSON_TMP}" + jq \ + --arg project_url "${PROJECT_URL}" \ + --arg project_name "${PROJECT_NAME}" \ + --arg publish_url "${PUBLISH_URL}" \ + --arg permalink_url "${PERMALINK_URL}" \ + --arg size "${PUBLISH_SIZE}" \ + --arg app_builder "${APP_BUILDER_SCRIPT_PATH}" \ + --arg app_builder_version "${APP_BUILDER_VERSION}" \ + '. + { project_url: $project_url, project_name: $project_name, publish_url: $publish_url, permalink_url: $permalink_url, size: $size, app_builder: $app_builder, app_builder_version: $app_builder_version}' "${NOTIFY_ASSET_JSON_TMP}" > "${OUTPUT_DIR}/asset-notify.json" + fi + + AWS_SHARED_CREDENTIALS_FILE="${CREDENTIALS}" AWS_CONFIG_FILE="${CONFIG}" aws s3 cp "${PUBLISH_S3_SOURCH_PATH}" "s3://${DEST_BUCKET_PATH}" --acl public-read --acl bucket-owner-full-control --recursive --exclude "*" --include "${PUBLISH_S3_INCLUDE}" --include "*.png" --include "*.json" + + echo "${PUBLISH_URL}" > "${OUTPUT_DIR}/publish_url.txt" +} + +publish_rclone() { + SECRETS_SUBDIR="rclone/${PUBLISHER}" + sync_secrets "${SECRETS_SUBDIR}" + CONFIG="${SECRETS_DIR}/rclone.conf" + RCLONE="rclone -v --config ${CONFIG}" + if [[ "${PUBLISH_CLOUD_REMOTE}" == "" ]]; then + PUBLISH_CLOUD_REMOTE=$(${RCLONE} config dump | jq -r 'keys_unsorted|.[0]') + if [[ "${PUBLISH_CLOUD_REMOTE}" == "" ]]; then + echo "ERROR: No PUBLISH_CLOUD_REMOTE or default remote in ${CONFIG}" + exit 2 + fi + fi + + # Detect the type of publish: apk, html, pwa + APK_COUNT=$(find "${ARTIFACTS_DIR}" -name "*.apk" | wc -l) + PUBLISH_FILE="" + if [[ ${APK_COUNT} -gt 0 ]]; then + # apk: publish all the apks + PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}/\*.apk" + elif [[ -f "${ARTIFACTS_DIR}/pwa.zip" ]]; then + # pwa: unzip the files to a directory and push the directory + mkdir "${ARTIFACTS_DIR}/pwa" + unzip "${ARTIFACTS_DIR}/pwa.zip" -d "${ARTIFACTS_DIR}/pwa" + PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}/pwa" + PUBLISH_FILE="" + elif [[ -f "${ARTIFACTS_DIR}/html.zip" ]]; then + # html: unzip the files to a directory and push the directory + mkdir "${ARTIFACTS_DIR}/html" + unzip "${ARTIFACTS_DIR}/html.zip" -d "${ARTIFACTS_DIR}/html" + PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}/html" + PUBLISH_FILE="index.html" + else + # Fallback to publishing all artifacts + PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}" + PUBLISH_CLOUD_BACKUP=0 + fi + + if [[ "${PUBLISH_CLOUD_COMMAND}" == "" ]]; then + PUBLISH_CLOUD_COMMAND=sync + fi + + if [[ "${PUBLISH_CLOUD_REMOTE_PATH}" == "" ]]; then + echo "ERROR: PUBLISH_CLOUD_REMOTE_PATH is not set" + exit 2 + fi + + PUBLISH_CLOUD_REMOTE_PATH_ALLOWED=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].remote_path_allowed") + + if [[ "${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED}" != "" ]]; then + [[ ${PUBLISH_CLOUD_REMOTE_PATH} =~ ${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED} ]] + if [[ "${#BASH_REMATCH[0]}" == "0" ]]; then + echo "ERROR: PUBLISH_CLOUD_REMOTE_PATH=${PUBLISH_CLOUD_REMOTE_PATH} doesn't match PUBLISH_CLOUD_REMOTE_PATH_ALLOWED=${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED}" + exit 2 + fi + fi + + if [[ "${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" == "" ]]; then + PUBLISH_CLOUD_BACKUP_REMOTE_PATH="backups" + fi + + # Add after the PUBLISH_CLOUD_REMOTE_PATH_ALLOWED has been check against PUBLISH_CLOUD_REMOTE_PATH + PUBLISH_CLOUD_REMOTE_ROOT=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].remote_root") + if [[ "${PUBLISH_CLOUD_REMOTE_ROOT}" == "null" ]]; then + # should be blank or end with slash so that it can be safely inserted below + PUBLISH_CLOUD_REMOTE_ROOT="" + else + #ensure that it ends with a slash + PUBLISH_CLOUD_REMOTE_ROOT="${PUBLISH_CLOUD_REMOTE_ROOT%/}/" + fi + + echo "PUBLISH_CLOUD_SOURCE_PATH=${PUBLISH_CLOUD_SOURCE_PATH}" + echo "PUBLISH_CLOUD_REMOTE=${PUBLISH_CLOUD_REMOTE}" + echo "PUBLISH_CLOUD_REMOTE_ROOT=${PUBLISH_CLOUD_REMOTE_ROOT}" + echo "PUBLISH_CLOUD_REMOTE_PATH=${PUBLISH_CLOUD_REMOTE_PATH}" + echo "PUBLISH_CLOUD_REMOTE_PATH_ALLOWED=${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED}" + echo "PUBLISH_CLOUD_BACKUP=${PUBLISH_CLOUD_BACKUP}" + echo "PUBLISH_CLOUD_BACKUP_ZIP=${PUBLISH_CLOUD_BACKUP_ZIP}" + echo "PUBLISH_CLOUD_BACKUP_REMOTE_PATH=${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" + + # if there are files to backup and backup is requested... + set +e + BACKUP_FILE_COUNT=$(${RCLONE} size "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" --json 2>/dev/null | jq -r ".count") + set -e + echo "Current file count: ${BACKUP_FILE_COUNT}" + if [[ "${PUBLISH_CLOUD_BACKUP}" == "1" && ${BACKUP_FILE_COUNT} -gt 0 ]]; then + DATE=$(date -u +"%Y-%m-%d_%H-%M-%S") + if [[ "${PUBLISH_CLOUD_BACKUP_ZIP}" == "1" && "${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" != "" ]]; then + # When performing a zip backup, we have to do download the current files to zip them and then re-upload them + # It is likely that the new files are similar to the old ones. So we will: + # 1. Sync the new files to the backup directory (seed the directory so only reverse diffs are copied) + ${RCLONE} sync "${PUBLISH_CLOUD_SOURCE_PATH}" "${ARTIFACTS_DIR}/Backup" + # 2. Sync the current files to the backup directory + ${RCLONE} sync "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" "${ARTIFACTS_DIR}/Backup" + # 3. Zip the files + BACKUP_FILENAME="$(basename "${PUBLISH_CLOUD_REMOTE_PATH}")-${DATE}.zip" + pushd "${ARTIFACTS_DIR}/Backup" + zip -qr ../"${BACKUP_FILENAME}" -- * + popd + # 4. Copy the backup to the Backup path + ${RCLONE} mkdir "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" + ${RCLONE} copy "${ARTIFACTS_DIR}/${BACKUP_FILENAME}" "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}/${PUBLISH_CLOUD_REMOTE_PATH}" + else + ${RCLONE} mkdir "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}/${PUBLISH_CLOUD_REMOTE_PATH}/${DATE}" + ${RCLONE} copy "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}/${PUBLISH_CLOUD_REMOTE_PATH}/${DATE}" + fi + fi + + ${RCLONE} mkdir "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" + # shellcheck disable=SC2086 + ${RCLONE} ${PUBLISH_CLOUD_COMMAND} -u "${PUBLISH_CLOUD_SOURCE_PATH}" "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" + + PUBLISH_BASE_URL=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].public_url") + if [[ "${PUBLISH_BASE_URL}" == "null" ]]; then + echo "ERROR: No public_url for rclone config ${PUBLISH_CLOUD_REMOTE}" + exit 2 + fi + PUBLISH_SERVER_PATH_ROOT=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].server_root") + PUBLISH_REMOTE_PATH="${PUBLISH_CLOUD_REMOTE_PATH}" + if [[ "${PUBLISH_SERVER_PATH_ROOT}" != "null" ]]; then + PUBLISH_REMOTE_PATH=${PUBLISH_REMOTE_PATH//$PUBLISH_SERVER_PATH_ROOT\//} + fi + if [[ "${PUBLISH_REMOTE_PATH}" == /* ]]; then + # If PUBLISH_REMOTE_PATH starts with a slash, remove it to avoid double slashes in the URL + PUBLISH_REMOTE_PATH="${PUBLISH_REMOTE_PATH:1}" + fi + PUBLISH_URL="${PUBLISH_BASE_URL}/${PUBLISH_REMOTE_PATH}/${PUBLISH_FILE}" + echo "${PUBLISH_URL}" > "${OUTPUT_DIR}/publish_url.txt" + + CLOUDFLARE_PURGE_CONFIG="${SECRETS_DIR}/cloudflare_purge.json" + if [ -f "${CLOUDFLARE_PURGE_CONFIG}" ]; then + CLOUDFLARE_ZONE=$(jq -r '.zone' "${CLOUDFLARE_PURGE_CONFIG}") + CLOUDFLARE_API_KEY=$(jq -r '.key' "${CLOUDFLARE_PURGE_CONFIG}") + + curl --request POST \ + --url "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE}/purge_cache" \ + --header 'Content-Type: application/json' \ + --header "Authorization: Bearer ${CLOUDFLARE_API_KEY}" \ + --data "{ \"files\" : [ \"${PUBLISH_URL}\" ]}" + fi +} + +publish_gradle() { + echo "Gradle $1" + if [ -f "${PROJECT_DIR}/publish.gradle" ]; then + pushd "$PROJECT_DIR" || exit 1 + gradle "$1" + popd || exit 1 + elif [ -f "${SCRIPT_DIR}/publish.gradle" ]; then + pushd "$SCRIPT_DIR" || exit 1 + gradle "$1" + popd || exit 1 + fi +} + +prepare_publish() { + PUBLISH_PROPERTIES="${ARTIFACTS_DIR}/publish-properties.json" + if [[ -f "${PUBLISH_PROPERTIES}" ]]; then + # Handle spaces in properties values + # https://stackoverflow.com/a/48513046/35577 + values=$(cat "${PUBLISH_PROPERTIES}") + while read -rd $'' line + do + echo "exporting ${line}" + export "${line?}" + done < <(jq -r <<<"$values" 'to_entries|map("\(.key)=\(.value)\u0000")[]') + fi + + PUBLISH_JSON="{}" + APP_BUILDER_VERSION="$(${APP_BUILDER_SCRIPT_PATH} "-?" | grep Version | cut -d\ -f2)" +} + +notify_scripture_earth_update_json() { + local i_json="$1" + local i_type="$2" + local i_url="$3" + + echo "${i_json}" | jq \ + --arg type "${i_type}" \ + --arg idx "${SCRIPTURE_EARTH_ID}" \ + --arg url "${i_url}" \ + --arg email "${PROJECT_OWNER_EMAIL}" \ + --arg projectName "${PROJECT_NAME}" \ + --arg username "${PROJECT_OWNER_NAME}" \ + --arg organization "${PROJECT_ORGANIZATION}" \ + --arg project "${PROJECT_URL}" \ + '. + [ { type: $type, idx: $idx, url: $url, email: $email, projectName: $projectName, username: $username, organization: $organization}]' +} + +notify_scripture_earth() { + local notify_json="$1" + # curl does not work! SE.org returns 406 Not Acceptable and can't figure out why. + wget \ + --header "Content-Type: application/json" \ + --header "Accept: application/json" \ + --post-data "${notify_json}" \ + "https://scriptureearth.org/api/add_resource.php?v=1&key=${SCRIPTURE_EARTH_KEY}" + +} + +publish_base_update_json() { + local i_json="$1" + + echo "${i_json}" | jq \ + --arg project_url "${PROJECT_URL}" \ + --arg project_name "${PROJECT_NAME}" \ + --arg product_name "${WORKFLOW_PRODUCT_NAME}" \ + --arg project_language "${PROJECT_LANGUAGE}" \ + --arg project_repo "${PROJECT_S3}" \ + --arg publish_url "${PUBLISH_URL}" \ + --arg permalink_url "${PERMALINK_URL}" \ + --arg size "${PUBLISH_SIZE}" \ + --arg app_builder "${APP_BUILDER_SCRIPT_PATH}" \ + --arg app_builder_version "${APP_BUILDER_VERSION}" \ + '. + { project_url: $project_url, project_name: $project_name, product_name: $product_name, project_language: $project_language, project_repo: $project_repo, publish_url: $publish_url, permalink_url: $permalink_url, size: $size, app_builder: $app_builder, app_builder_version: $app_builder_version }' +} + +post_publish() { + PUBLISH_JSON="$(publish_base_update_json "${PUBLISH_JSON}")" + + # The WORKFLOW_PRODUCT_NAME is a user generated name and we are expecting the name to + # contain the keywords: ios, android, google (play), and pwa. So the system admin + # should be aware when adding new products. + WORKFLOW_PRODUCT_NAME_LOWER=$(echo "${WORKFLOW_PRODUCT_NAME}" | awk '{print tolower($0)}') + if [[ "${PUBLISH_NOTIFY}" != "" ]]; then + # See S1: Services/BuildEngine: BuildEngineServiceBase::AddProductProperitiesToEnvironment + # See S2: node-server/job-executors/common.build-publish.ts: addProductPropertiesToEnvironment + # for the list of properties that are added. + + for NOTIFY_SERVER in ${PUBLISH_NOTIFY/,/ } + do + # Notify Scripture Earth + if [[ "${NOTIFY_SERVER})" == *"SCRIPTURE_EARTH"* && "${SCRIPTURE_EARTH_KEY}" != "" ]]; then + EMPTY_NOTIFY_JSON="[]" + NOTIFY_JSON=$EMPTY_NOTIFY_JSON + if [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"ios"* ]]; then + # Send Notification for iOS Asset Package + NOTIFY_TYPE="ios" + # change protocol to "asset://" so that container app will recognize as asset-package + # shellcheck disable=SC2001 + NOTIFY_URL="$(sed -e 's/https*:/asset:/' <<< "$UI_URL")/api/products/${PRODUCT_ID}/files/published/asset-package" + NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" + elif [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"android"* ]]; then + # Send Notification for Android app to Google Play + NOTIFY_TYPE="apk" + NOTIFY_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/apk" + NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" + if [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"google"* ]]; then + # Send additional notification for Android app to Google Play + NOTIFY_TYPE="google_play" + NOTIFY_URL="${PUBLISH_URL}" + NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" + fi + elif [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"pwa"* ]]; then + # Send Notification for PWA + NOTIFY_TYPE="sab_html" + NOTIFY_URL="${PUBLISH_URL}" + NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" + fi + + if [[ "${NOTIFY_JSON}" != "{$EMPTY_NOTIFY_JSON}" ]]; then + notify_scripture_earth "${NOTIFY_JSON}" + fi + else + if [[ -f "${OUTPUT_DIR}/asset-notify.json" ]]; then + echo "NOTIFY: ${NOTIFY_SERVER}" + SECRETS_SUBDIR="notify/${NOTIFY_SERVER}" + sync_secrets "${SECRETS_SUBDIR}" + ENDPOINT="${SECRETS_DIR}/endpoint.json" + echo "ENDPOINT: ${ENDPOINT}" + find "${SECRETS_DIR}" + if [[ -f "${ENDPOINT}" ]]; then + cat "${ENDPOINT}" + NOTIFY_URL=$(jq -r '.url' "${ENDPOINT}") + NOTIFY_ARGS_FILE=$(mktemp) + # How to loop json array in bash + # https://www.starkandwayne.com/blog/bash-for-loop-over-json-array-using-jq/ + NOTIFY_HEADERS=$(jq -r '.headers[] | @base64' "${ENDPOINT}") + for header in $NOTIFY_HEADERS + do + echo "header = \"$(echo "$header" | base64 --decode)\"" >> "${NOTIFY_ARGS_FILE}" + done + echo "header = \"Accept: application/json\"" >> "${NOTIFY_ARGS_FILE}" + echo "header = \"Content-Type: application/json\"" >> "${NOTIFY_ARGS_FILE}" + NOTIFY_JSON=$(cat "${OUTPUT_DIR}/asset-notify.json") + curl -X POST --config "${NOTIFY_ARGS_FILE}" --data "${NOTIFY_JSON}" "${NOTIFY_URL}" + fi + fi + fi + done + fi + + echo "${PUBLISH_JSON}" > "${OUTPUT_DIR}/publish.json" +} + +APK_FILES=( "${ARTIFACTS_DIR}"/*.apk ) +AAB_FILES=( "${ARTIFACTS_DIR}"/*.aab ) + +prepare_publish +env | sort +echo "TARGETS: $TARGETS" +for target in $TARGETS +do + case "$target" in + "google-play") publish_google_play ;; + "rclone") publish_rclone ;; + "s3-bucket") publish_s3_bucket ;; + *) publish_gradle "$target" ;; + esac +done + +post_publish diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index b1bbf6b2..f47d869a 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -414,7 +414,7 @@ export class CodeBuild extends AWSCommon { * @param Array source Strings defining the source parameter of the build * */ - public createProject( + public async createProject( base_name: string, role_arn: string, cache: ProjectCache, @@ -423,7 +423,7 @@ export class CodeBuild extends AWSCommon { const project_name = CodeBuild.getCodeBuildProjectName(base_name); const artifacts_bucket = CodeBuild.getArtifactsBucket(); console.log(`Bucket: ${artifacts_bucket}`); - this.codeBuildClient.send( + return await this.codeBuildClient.send( new CreateProjectCommand({ artifacts: { // REQUIRED diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index 1668c943..cab30493 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -10,6 +10,7 @@ import { type _Object } from '@aws-sdk/client-s3'; import { basename, dirname, extname } from 'node:path'; +import { S3SyncClient } from 's3-sync-client'; import { AWSCommon } from './common'; import { env } from '$env/dynamic/private'; import { @@ -226,6 +227,11 @@ export class S3 extends AWSCommon { return this.deleteMatchingObjects(s3Bucket, s3Folder); } + public async uploadFolder(folderName: string, bucket: string) { + const client = new S3SyncClient({ client: this.getS3ClientWithCredentials() }); + return await client.sync(folderName, bucket); + } + private getFileType(fileName: string) { switch (extname(fileName)) { case 'html': diff --git a/src/lib/server/bullmq/BullMQ.ts b/src/lib/server/bullmq/BullMQ.ts index af1bc1b2..74cfc00a 100644 --- a/src/lib/server/bullmq/BullMQ.ts +++ b/src/lib/server/bullmq/BullMQ.ts @@ -8,5 +8,6 @@ export const allWorkers = building new Workers.S3(), new Workers.Projects(), new Workers.Publishing(), - new Workers.Polling() + new Workers.Polling(), + new Workers.SystemStartup() ]; diff --git a/src/lib/server/bullmq/BullWorker.ts b/src/lib/server/bullmq/BullWorker.ts index 4025565b..0e3c09e6 100644 --- a/src/lib/server/bullmq/BullWorker.ts +++ b/src/lib/server/bullmq/BullWorker.ts @@ -1,7 +1,7 @@ import type { Job } from 'bullmq'; import { Worker } from 'bullmq'; import * as Executor from '../job-executors'; -import { getWorkerConfig } from './queues'; +import { getQueues, getWorkerConfig } from './queues'; import * as BullMQ from './types'; import { building } from '$app/environment'; @@ -22,6 +22,37 @@ export abstract class BullWorker { abstract run(job: Job): Promise; } +export class SystemStartup extends BullWorker { + private jobsLeft = 0; + constructor() { + super(BullMQ.QueueName.System_Startup); + const startupJobs = [ + [ + 'Create CodeBuild Project (Startup)', + { + type: BullMQ.JobType.System_CreateCodeBuildProject + } + ] + ] as const; + startupJobs.forEach(([name, data]) => { + getQueues().SystemStartup.add(name, data); + }); + this.jobsLeft = startupJobs.length; + } + async run(job: Job) { + // Close the worker after running the startup jobs + // This prevents this worker from taking startup jobs when a new instance is started + // The worker will not actually be closed until all processing jobs are complete + if (--this.jobsLeft === 0) this.worker?.close(); + switch (job.data.type) { + case BullMQ.JobType.System_CreateCodeBuildProject: + return Executor.System.createCodeBuildProject( + job as Job + ); + } + } +} + export class Builds extends BullWorker { constructor() { super(BullMQ.QueueName.Builds); diff --git a/src/lib/server/bullmq/queues.ts b/src/lib/server/bullmq/queues.ts index 149b44a9..1ccfaac1 100644 --- a/src/lib/server/bullmq/queues.ts +++ b/src/lib/server/bullmq/queues.ts @@ -1,6 +1,6 @@ import { Queue } from 'bullmq'; import { Redis } from 'ioredis'; -import type { BuildJob, PollJob, ProjectJob, PublishJob, S3Job } from './types'; +import type { BuildJob, PollJob, ProjectJob, PublishJob, S3Job, SystemJob } from './types'; import { QueueName } from './types'; class Connection { @@ -89,12 +89,15 @@ function createQueues() { const Publishing = new Queue(QueueName.Publishing, getQueueConfig()); /** Queue for jobs that poll BuildEngine, such as checking the status of a build */ const Polling = new Queue(QueueName.Polling, getQueueConfig()); + /** Queue for jobs that run on startup, such as creating the CodeBuild project */ + const SystemStartup = new Queue(QueueName.System_Startup, getQueueConfig()); return { Builds, S3, Projects, Publishing, - Polling + Polling, + SystemStartup }; } export function getQueues() { diff --git a/src/lib/server/bullmq/types.ts b/src/lib/server/bullmq/types.ts index 850b6f45..324cec60 100644 --- a/src/lib/server/bullmq/types.ts +++ b/src/lib/server/bullmq/types.ts @@ -20,7 +20,8 @@ export enum QueueName { S3 = 'S3', Projects = 'Projects', Publishing = 'Publishing', - Polling = 'Polling' + Polling = 'Polling', + System_Startup = 'System (Startup)' } export enum JobType { @@ -37,7 +38,9 @@ export enum JobType { Publish_PostProcess = 'Postprocess Publish', // S3 Jobs S3_CopyArtifacts = 'Copy Artifacts to S3', - S3_CopyError = 'Copy Errors to S3' + S3_CopyError = 'Copy Errors to S3', + // System Jobs + System_CreateCodeBuildProject = 'Create CodeBuild Project' } export namespace Build { @@ -108,6 +111,12 @@ export namespace S3 { } } +export namespace System { + export interface CreateCodeBuildProject { + type: JobType.System_CreateCodeBuildProject; + } +} + export type Job = JobTypeMap[keyof JobTypeMap]; export type BuildJob = JobTypeMap[JobType.Build_Product | JobType.Build_PostProcess]; @@ -115,6 +124,7 @@ export type S3Job = JobTypeMap[JobType.S3_CopyArtifacts | JobType.S3_CopyError]; export type PublishJob = JobTypeMap[JobType.Publish_Product | JobType.Publish_PostProcess]; export type PollJob = JobTypeMap[JobType.Poll_Build | JobType.Poll_Publish]; export type ProjectJob = JobTypeMap[JobType.Project_Create]; +export type SystemJob = JobTypeMap[JobType.System_CreateCodeBuildProject]; export type JobTypeMap = { [JobType.Build_Product]: Build.Product; @@ -126,5 +136,6 @@ export type JobTypeMap = { [JobType.Publish_PostProcess]: Publish.PostProcess; [JobType.S3_CopyArtifacts]: S3.CopyArtifacts; [JobType.S3_CopyError]: S3.CopyErrors; + [JobType.System_CreateCodeBuildProject]: System.CreateCodeBuildProject; // Add more mappings here as needed }; diff --git a/src/lib/server/job-executors/index.ts b/src/lib/server/job-executors/index.ts index 9583389a..a570b46b 100644 --- a/src/lib/server/job-executors/index.ts +++ b/src/lib/server/job-executors/index.ts @@ -3,3 +3,4 @@ export * as Polling from './polling'; export * as Project from './project'; export * as Publish from './publish'; export * as S3 from './s3'; +export * as System from './system'; diff --git a/src/lib/server/job-executors/system.ts b/src/lib/server/job-executors/system.ts new file mode 100644 index 00000000..9a727f42 --- /dev/null +++ b/src/lib/server/job-executors/system.ts @@ -0,0 +1,107 @@ +import type { ProjectCache, ProjectSource } from '@aws-sdk/client-codebuild'; +import type { Job } from 'bullmq'; +import { join } from 'node:path'; +import { CodeBuild } from '../aws/codebuild'; +import { IAmWrapper } from '../aws/iamwrapper'; +import { S3 } from '../aws/s3'; +import type { BullMQ } from '../bullmq'; + +type Logger = (msg: string) => void; + +export async function createCodeBuildProject( + job: Job +): Promise { + try { + const build = await createProject( + 'build_app', + { + location: S3.getArtifactsBucket() + '/codebuild-cache', + type: 'S3' + }, + { + buildspec: 'version: 0.2', + gitCloneDepth: 1, + location: 'https://git-codecommit.us-east-1.amazonaws.com/v1/repos/sample', + type: 'CODECOMMIT' + }, + (msg) => job.log(msg) + ); + + job.updateProgress(25); + + // Build publish role if necessary + // Copy default file + const project = copyFolder( + join(process.cwd(), './scripts/project_default'), + 's3://' + S3.getArtifactsBucket(), + (msg) => job.log(msg) + ); + + job.updateProgress(50); + + const publish = await createProject( + 'publish_app', + { + type: 'NO_CACHE' + }, + { + buildspec: 'version: 0.2', + gitCloneDepth: 1, + location: `arn:aws:s3:::${S3.getArtifactsBucket()}/default/default.zip`, + type: 'S3' + }, + (msg) => job.log(msg) + ); + + job.updateProgress(75); + + const scripts = await copyFolder( + join(process.cwd(), './scripts/upload'), + 's3://' + S3.getProjectsBucket(), + (msg) => job.log(msg) + ); + + job.updateProgress(100); + return { + build, + project, + publish, + scripts + }; + } catch (e) { + job.log(`${e}`); + } + return; +} + +async function createProject( + projectName: string, + cache: ProjectCache, + source: ProjectSource, + log: Logger +) { + try { + const codeBuild = new CodeBuild(); + const iamWrapper = new IAmWrapper(); + if (!(await codeBuild.projectExists(projectName))) { + log('Creating build project ' + projectName); + + const roleArn = await iamWrapper.getRoleArn(projectName); + const res = await codeBuild.createProject(projectName, roleArn, cache, source); + log('Project created'); + return res; + } + } catch (e) { + log(`${e}`); + } +} +async function copyFolder(sourceFolder: string, bucket: string, log: Logger) { + try { + const s3 = new S3(); + const res = await s3.uploadFolder(sourceFolder, bucket); + log('Copy completed'); + return res; + } catch (e) { + log(`${e}`); + } +} From 307ba76fb825b182c0a657fda78b86aa54e60b58 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 29 Oct 2025 16:39:53 -0500 Subject: [PATCH 087/144] Add missing migration --- .../prisma/migrations/01_add_missing_auto_date/migration.sql | 2 ++ src/lib/prisma/schema.prisma | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 src/lib/prisma/migrations/01_add_missing_auto_date/migration.sql diff --git a/src/lib/prisma/migrations/01_add_missing_auto_date/migration.sql b/src/lib/prisma/migrations/01_add_missing_auto_date/migration.sql new file mode 100644 index 00000000..85063288 --- /dev/null +++ b/src/lib/prisma/migrations/01_add_missing_auto_date/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "public"."build" ALTER COLUMN "created" SET DEFAULT CURRENT_TIMESTAMP; diff --git a/src/lib/prisma/schema.prisma b/src/lib/prisma/schema.prisma index a59db090..aa404086 100644 --- a/src/lib/prisma/schema.prisma +++ b/src/lib/prisma/schema.prisma @@ -13,8 +13,8 @@ model build { status String? @db.VarChar(255) result String? @db.VarChar(255) error String? @db.VarChar(2083) - created DateTime? @db.Timestamp(6) - updated DateTime? @db.Timestamp(6) + created DateTime? @default(now()) @db.Timestamp(6) + updated DateTime? @updatedAt @db.Timestamp(6) channel String? @db.VarChar(255) version_code Int? artifact_url_base String? @db.VarChar(2083) From b14670d06f0a6da09e248412f97eed5208ecad9a Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 12 Nov 2025 11:25:33 -0600 Subject: [PATCH 088/144] Fix Builds artifact view --- src/routes/build-admin/view/+page.server.ts | 4 +++- src/routes/build-admin/view/+page.svelte | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/build-admin/view/+page.server.ts b/src/routes/build-admin/view/+page.server.ts index c5715741..ca70ef41 100644 --- a/src/routes/build-admin/view/+page.server.ts +++ b/src/routes/build-admin/view/+page.server.ts @@ -1,6 +1,7 @@ import { error } from '@sveltejs/kit'; import * as v from 'valibot'; import type { PageServerLoad } from './$types'; +import { Build } from '$lib/models/build'; import { prisma } from '$lib/server/prisma'; import { idSchema, paramNumber } from '$lib/valibot'; @@ -19,6 +20,7 @@ export const load = (async ({ url }) => { if (!build) error(404); return { - build + build, + artifacts: build.artifact_files ? Build.artifacts(build) : {} }; }) satisfies PageServerLoad; diff --git a/src/routes/build-admin/view/+page.svelte b/src/routes/build-admin/view/+page.svelte index 688e6a45..7a30658a 100644 --- a/src/routes/build-admin/view/+page.svelte +++ b/src/routes/build-admin/view/+page.svelte @@ -2,7 +2,6 @@ import type { PageData } from './$types'; import { page } from '$app/state'; import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; - import { Build } from '$lib/models/build'; import { title } from '$lib/stores'; import { getTimeDateString } from '$lib/utils/time'; @@ -59,7 +58,7 @@ Artifacts {#if data.build.artifact_files} - {#each Object.entries(Build.artifacts(data.build)) + {#each Object.entries(data.artifacts) .filter(([_, url]) => !!url) .sort(([a, _1], [b, _2]) => a.localeCompare(b, 'en-US')) as [name, url]} {name} From 9cf04ae06f4aaf4f59d1be53e4379b22bf315916 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 12 Nov 2025 11:25:52 -0600 Subject: [PATCH 089/144] Fix build polling --- src/lib/server/job-executors/build.ts | 10 +++++++++- src/lib/server/job-executors/polling.ts | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index b6eec724..ff251f5a 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -5,7 +5,7 @@ import { join } from 'node:path'; import { Build } from '../../models/build'; import { CodeBuild } from '../aws/codebuild'; import { CodeCommit } from '../aws/codecommit'; -import type { BullMQ } from '../bullmq'; +import { BullMQ, getQueues } from '../bullmq'; import { prisma } from '../prisma'; export async function product(job: Job): Promise { @@ -110,6 +110,14 @@ export async function product(job: Job): Promise } }); } + const name = `Check status of Build #${build.id}`; + await getQueues().Polling.upsertJobScheduler(name, BullMQ.RepeatEveryMinute, { + name, + data: { + type: BullMQ.JobType.Poll_Build, + buildId: build.id + } + }); job.updateProgress(100); return { versionCode, diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index 7b162973..a1ec683d 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -30,6 +30,7 @@ export async function build(job: Job): Promise { job.updateProgress(50); if (codeBuild.isBuildComplete(buildStatus)) { + await getQueues().Polling.removeJobScheduler(job.name); build.status = Build.Status.PostProcessing; status = codeBuild.getStatus(buildStatus); switch (status) { From 970f93b2b0cc6d2df04ec972cf911ed4a9c9aeb8 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 12 Nov 2025 11:27:00 -0600 Subject: [PATCH 090/144] Small fixes in S3 wrapper --- src/lib/server/aws/s3.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index cab30493..85a32875 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -107,11 +107,10 @@ export class S3 extends AWSCommon { */ public async copyS3Folder(artifacts_provider: ProviderForPrefix & ProviderForArtifacts) { const artifactsBucket = S3.getArtifactsBucket(); - const sourcePrefix = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; - const destPrefix = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()) + '/'; - const publicBaseUrl = `https://${artifactsBucket}.s3.amazonaws.com/${destPrefix}`; - beginArtifacts(artifacts_provider, publicBaseUrl); const destFolderPrefix = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()); + const sourcePrefix = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; + const destPrefix = destFolderPrefix + '/'; + beginArtifacts(artifacts_provider, `https://${artifactsBucket}.s3.amazonaws.com/${destPrefix}`); try { await this.deleteMatchingObjects(artifactsBucket, destFolderPrefix); } catch (e) { @@ -259,13 +258,15 @@ export class S3 extends AWSCommon { Prefix }) ); - return await this.s3Client.send( - new DeleteObjectsCommand({ - Bucket, - Delete: { - Objects: existing.Contents?.map(({ Key }) => ({ Key })) - } - }) - ); + if (existing.Contents) { + return await this.s3Client.send( + new DeleteObjectsCommand({ + Bucket, + Delete: { + Objects: existing.Contents?.map(({ Key }) => ({ Key })) + } + }) + ); + } } } From 0d1303583ac1335a89b9e45c968911a2dbf38625 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 2 Feb 2026 10:42:26 -0600 Subject: [PATCH 091/144] Use Scriptoria for Authentication (#72) * Install packages * Initial (non-functional) config * Move UI to group folder * Delete auth file * Use APP_ENV in queue prefix * Return stub info for versions * Move favicon/css import to root layout * Uninstall authjs, use jose directly * Basic authentication through Scriptoria * Add UI for login/signout * Update note in package.json * Enforce scope=admin on returned token --- package-lock.json | 17 +++- package.json | 5 + src/app.d.ts | 1 + src/hooks.server.ts | 39 ++++---- src/lib/icons/ScriptoriaIcon.svelte | 35 +++++++ src/lib/server/auth.ts | 99 +++++++++++++++++++ src/lib/server/bullmq/queues.ts | 16 ++- src/routes/(api)/system/check/+server.ts | 2 +- src/routes/(auth)/+layout.svelte | 16 +++ src/routes/(auth)/exchange/+server.ts | 60 +++++++++++ src/routes/(auth)/login/+page.server.ts | 15 +++ src/routes/(auth)/login/+page.svelte | 65 ++++++++++++ src/routes/(auth)/signout/+page.server.ts | 6 ++ src/routes/(ui)/+layout.server.ts | 5 + src/routes/(ui)/+layout.svelte | 81 +++++++++++++++ src/routes/{ => (ui)}/+page.svelte | 0 src/routes/{ => (ui)}/about/+page.svelte | 0 .../{ => (ui)}/build-admin/+page.server.ts | 0 .../{ => (ui)}/build-admin/+page.svelte | 0 .../build-admin/update/+page.server.ts | 0 .../build-admin/update/+page.svelte | 0 .../build-admin/view/+page.server.ts | 0 .../{ => (ui)}/build-admin/view/+page.svelte | 0 .../{ => (ui)}/client-admin/+page.server.ts | 0 .../{ => (ui)}/client-admin/+page.svelte | 0 .../client-admin/create/+page.server.ts | 0 .../client-admin/create/+page.svelte | 0 .../client-admin/update/+page.server.ts | 0 .../client-admin/update/+page.svelte | 0 src/routes/{ => (ui)}/client-admin/valibot.ts | 0 .../client-admin/view/+page.server.ts | 0 .../{ => (ui)}/client-admin/view/+page.svelte | 0 .../{ => (ui)}/job-admin/+page.server.ts | 0 src/routes/{ => (ui)}/job-admin/+page.svelte | 0 .../job-admin/update/+page.server.ts | 0 .../{ => (ui)}/job-admin/update/+page.svelte | 0 .../{ => (ui)}/job-admin/view/+page.server.ts | 0 .../{ => (ui)}/job-admin/view/+page.svelte | 0 .../{ => (ui)}/project-admin/+page.server.ts | 0 .../{ => (ui)}/project-admin/+page.svelte | 0 .../project-admin/update/+page.server.ts | 0 .../project-admin/update/+page.svelte | 0 .../project-admin/view/+page.server.ts | 0 .../project-admin/view/+page.svelte | 0 .../queue-admin/[...rest]/+page.svelte | 0 .../{ => (ui)}/release-admin/+page.server.ts | 0 .../{ => (ui)}/release-admin/+page.svelte | 0 .../release-admin/update/+page.server.ts | 0 .../release-admin/update/+page.svelte | 0 .../release-admin/view/+page.server.ts | 0 .../release-admin/view/+page.svelte | 0 src/routes/+layout.svelte | 40 +------- 52 files changed, 437 insertions(+), 65 deletions(-) create mode 100644 src/lib/icons/ScriptoriaIcon.svelte create mode 100644 src/lib/server/auth.ts create mode 100644 src/routes/(auth)/+layout.svelte create mode 100644 src/routes/(auth)/exchange/+server.ts create mode 100644 src/routes/(auth)/login/+page.server.ts create mode 100644 src/routes/(auth)/login/+page.svelte create mode 100644 src/routes/(auth)/signout/+page.server.ts create mode 100644 src/routes/(ui)/+layout.server.ts create mode 100644 src/routes/(ui)/+layout.svelte rename src/routes/{ => (ui)}/+page.svelte (100%) rename src/routes/{ => (ui)}/about/+page.svelte (100%) rename src/routes/{ => (ui)}/build-admin/+page.server.ts (100%) rename src/routes/{ => (ui)}/build-admin/+page.svelte (100%) rename src/routes/{ => (ui)}/build-admin/update/+page.server.ts (100%) rename src/routes/{ => (ui)}/build-admin/update/+page.svelte (100%) rename src/routes/{ => (ui)}/build-admin/view/+page.server.ts (100%) rename src/routes/{ => (ui)}/build-admin/view/+page.svelte (100%) rename src/routes/{ => (ui)}/client-admin/+page.server.ts (100%) rename src/routes/{ => (ui)}/client-admin/+page.svelte (100%) rename src/routes/{ => (ui)}/client-admin/create/+page.server.ts (100%) rename src/routes/{ => (ui)}/client-admin/create/+page.svelte (100%) rename src/routes/{ => (ui)}/client-admin/update/+page.server.ts (100%) rename src/routes/{ => (ui)}/client-admin/update/+page.svelte (100%) rename src/routes/{ => (ui)}/client-admin/valibot.ts (100%) rename src/routes/{ => (ui)}/client-admin/view/+page.server.ts (100%) rename src/routes/{ => (ui)}/client-admin/view/+page.svelte (100%) rename src/routes/{ => (ui)}/job-admin/+page.server.ts (100%) rename src/routes/{ => (ui)}/job-admin/+page.svelte (100%) rename src/routes/{ => (ui)}/job-admin/update/+page.server.ts (100%) rename src/routes/{ => (ui)}/job-admin/update/+page.svelte (100%) rename src/routes/{ => (ui)}/job-admin/view/+page.server.ts (100%) rename src/routes/{ => (ui)}/job-admin/view/+page.svelte (100%) rename src/routes/{ => (ui)}/project-admin/+page.server.ts (100%) rename src/routes/{ => (ui)}/project-admin/+page.svelte (100%) rename src/routes/{ => (ui)}/project-admin/update/+page.server.ts (100%) rename src/routes/{ => (ui)}/project-admin/update/+page.svelte (100%) rename src/routes/{ => (ui)}/project-admin/view/+page.server.ts (100%) rename src/routes/{ => (ui)}/project-admin/view/+page.svelte (100%) rename src/routes/{ => (ui)}/queue-admin/[...rest]/+page.svelte (100%) rename src/routes/{ => (ui)}/release-admin/+page.server.ts (100%) rename src/routes/{ => (ui)}/release-admin/+page.svelte (100%) rename src/routes/{ => (ui)}/release-admin/update/+page.server.ts (100%) rename src/routes/{ => (ui)}/release-admin/update/+page.svelte (100%) rename src/routes/{ => (ui)}/release-admin/view/+page.server.ts (100%) rename src/routes/{ => (ui)}/release-admin/view/+page.svelte (100%) diff --git a/package-lock.json b/package-lock.json index 057e9239..ef4d23e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "eslint-plugin-svelte": "^3.0.0", "globals": "^16.0.0", "hono": "^4.10.1", + "jose": "^6.1.3", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.11", @@ -6192,9 +6193,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -8206,6 +8207,16 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", diff --git a/package.json b/package.json index 23482113..26c0fedf 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "eslint-plugin-svelte": "^3.0.0", "globals": "^16.0.0", "hono": "^4.10.1", + "jose": "^6.1.3", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.11", @@ -56,5 +57,9 @@ "@prisma/client": "^6.15.0", "prisma": "^6.15.0", "s3-sync-client": "^4.3.1" + }, + "//cookie": "sveltekit depends on v0.6, which is insecure", + "overrides": { + "cookie": "^0.7.0" } } diff --git a/src/app.d.ts b/src/app.d.ts index 6fb018ae..e2b53922 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -5,6 +5,7 @@ declare global { // interface Error {} interface Locals { clientId: number; + userEmail?: string; } // interface PageData {} // interface PageState {} diff --git a/src/hooks.server.ts b/src/hooks.server.ts index d20d3055..e7ef09cd 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,28 +1,25 @@ import { type Handle, error } from '@sveltejs/kit'; import { sequence } from '@sveltejs/kit/hooks'; import { building } from '$app/environment'; +import { tryVerifyAPIToken, tryVerifyCookie } from '$lib/server/auth'; import { QueueConnected, getQueues } from '$lib/server/bullmq'; import { bullboardHandle } from '$lib/server/bullmq/BullBoard'; import { allWorkers } from '$lib/server/bullmq/BullMQ'; -import { DatabaseConnected, prisma } from '$lib/server/prisma'; -import { ErrorResponse } from '$lib/utils'; +import { DatabaseConnected } from '$lib/server/prisma'; const handleAPIRoute: Handle = async ({ event, resolve }) => { - if (event.route.id?.split('/')[1] === '(api)') { - if (event.request.headers.get('Content-Type') !== 'application/json') { - return ErrorResponse(400, 'Missing Header Content-Type: application/json'); - } - const access_token = event.request.headers.get('Authorization')?.replace('Bearer ', ''); - if (!access_token) { - return ErrorResponse(401, 'Missing Header Authorization: Bearer '); - } - const client = await prisma.client.findFirst({ where: { access_token } }); - if (!client) { - return ErrorResponse(403, 'Invalid Access Token'); - } - event.locals.clientId = client.id; - } else { - event.locals.clientId = 0; + const [success, res] = await tryVerifyAPIToken(event); + if (!success) { + return res; + } + event.locals.clientId = res.id; + return resolve(event); +}; + +const handleAuthRoute: Handle = async ({ event, resolve }) => { + event.locals.clientId = 0; + if (event.route.id?.split('/')?.[1] !== '(auth)') { + await tryVerifyCookie(event); } return resolve(event); }; @@ -62,5 +59,11 @@ export const handle: Handle = async ({ event, resolve }) => { return new Response('', { status: 404 }); } - return await sequence(heartbeat, handleAPIRoute, bullboardHandle)({ event, resolve }); + return await sequence( + heartbeat, + (h) => { + return event.route.id?.split('/')?.[1] === '(api)' ? handleAPIRoute(h) : handleAuthRoute(h); + }, + bullboardHandle + )({ event, resolve }); }; diff --git a/src/lib/icons/ScriptoriaIcon.svelte b/src/lib/icons/ScriptoriaIcon.svelte new file mode 100644 index 00000000..61a31370 --- /dev/null +++ b/src/lib/icons/ScriptoriaIcon.svelte @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts new file mode 100644 index 00000000..84d8e377 --- /dev/null +++ b/src/lib/server/auth.ts @@ -0,0 +1,99 @@ +import type { Prisma } from '@prisma/client'; +import { type RequestEvent, error, redirect } from '@sveltejs/kit'; +import { jwtDecrypt } from 'jose'; +import { createHash, randomUUID } from 'node:crypto'; +import { getAuthConnection } from './bullmq/queues'; +import { prisma } from './prisma'; +import { env as secrets } from '$env/dynamic/private'; +import { env } from '$env/dynamic/public'; +import { ErrorResponse } from '$lib/utils'; + +export async function tryVerifyCookie(event: RequestEvent, gotoLoginPage = true) { + const cookie = event.cookies.get('scriptoria.session-token'); + + let token = null; + try { + if (cookie) { + token = await jwtDecrypt(cookie, new TextEncoder().encode(secrets.AUTH0_SECRET)); + + event.locals.userEmail = token.payload.email as string; + } + } catch { + /* empty */ + } + + if (token && token.payload.scope !== 'admin') { + invalidateLogin(event, false); + throw error(401, 'Bad Login'); + } + + if (!cookie || !token) { + if (gotoLoginPage) { + const returnTo = event.url.pathname + event.url.search; + throw redirect(302, `/login?returnTo=${encodeURIComponent(returnTo)}`); + } else { + throw await initiateScriptoriaLogin(event); + } + } +} + +async function initiateScriptoriaLogin(event: RequestEvent) { + const verify = randomUUID(); + const requestId = randomUUID(); + + await getAuthConnection().set(`${requestId}`, verify, 'EX', 300); // 5 minute (300 s) TTL + + const hash = createHash('sha256'); + hash.update(verify); + const challenge = hash.digest('base64url').replace(/=+$/, ''); + + const returnTo = event.url.searchParams.get('returnTo'); + + throw redirect( + 302, + `${env.PUBLIC_SCRIPTORIA_URL}/api/auth/token?` + + `challenge=${challenge}&` + + `redirect_uri=${encodeURIComponent( + `${secrets.ORIGIN}/exchange?` + + (returnTo ? `returnTo=${returnTo}` : '') + + `&requestId=${requestId}` + )}&` + + `scope=admin` + ); +} + +export function returnTo(event: RequestEvent) { + let redirectUrl = decodeURIComponent(event.url.searchParams.get('returnTo') ?? ''); + while (redirectUrl?.startsWith('/login')) { + redirectUrl = decodeURIComponent(new URL(redirectUrl).searchParams.get('returnTo') ?? ''); + } + throw redirect( + 302, + redirectUrl && redirectUrl.startsWith('/') && !redirectUrl.startsWith('//') ? redirectUrl : '/' + ); +} + +export function invalidateLogin(event: RequestEvent, redirectToLogin = true) { + event.cookies.set('scriptoria.session-token', '', { path: '/' }); + if (redirectToLogin) { + throw redirect(302, '/login'); + } +} + +export async function tryVerifyAPIToken( + event: RequestEvent +): Promise<[true, Prisma.clientGetPayload] | [false, Response]> { + if (event.request.headers.get('Content-Type') !== 'application/json') { + return [false, ErrorResponse(400, 'Missing Header Content-Type: application/json')]; + } + const access_token = event.request.headers.get('Authorization')?.replace('Bearer ', ''); + if (!access_token) { + return [false, ErrorResponse(401, 'Missing Header Authorization: Bearer ')]; + } + const client = await prisma.client.findFirst({ where: { access_token } }); + if (!client) { + return [false, ErrorResponse(403, 'Invalid Access Token')]; + } + + return [true, client]; +} diff --git a/src/lib/server/bullmq/queues.ts b/src/lib/server/bullmq/queues.ts index 1ccfaac1..b38c3b8b 100644 --- a/src/lib/server/bullmq/queues.ts +++ b/src/lib/server/bullmq/queues.ts @@ -2,14 +2,16 @@ import { Queue } from 'bullmq'; import { Redis } from 'ioredis'; import type { BuildJob, PollJob, ProjectJob, PublishJob, S3Job, SystemJob } from './types'; import { QueueName } from './types'; +import { env } from '$env/dynamic/private'; class Connection { private conn: Redis; private connected: boolean; - constructor(isQueueConnection = false) { + constructor(isQueueConnection = false, keyPrefix?: string) { this.conn = new Redis({ host: process.env.NODE_ENV === 'development' ? 'localhost' : process.env.VALKEY_HOST, - maxRetriesPerRequest: isQueueConnection ? undefined : null + maxRetriesPerRequest: isQueueConnection ? undefined : null, + keyPrefix }); this.connected = false; this.conn.on('close', () => { @@ -55,14 +57,20 @@ class Connection { let _workerConnection: Connection | undefined = undefined; let _queueConnection: Connection | undefined = undefined; +let _authConnection: Connection | undefined = undefined; export const QueueConnected = () => _queueConnection?.IsConnected() ?? false; +export const getAuthConnection = () => { + if (!_authConnection) _authConnection = new Connection(false, env.APP_ENV + '_be_auth'); + return _authConnection.connection(); +}; + export const getWorkerConfig = () => { if (!_workerConnection) _workerConnection = new Connection(false); return { connection: _workerConnection!.connection(), - prefix: 'build-engine' + prefix: env.APP_ENV + '_build-engine' } as const; }; @@ -70,7 +78,7 @@ export const getQueueConfig = () => { if (!_queueConnection) _queues = createQueues(); return { connection: _queueConnection!.connection(), - prefix: 'build-engine' + prefix: env.APP_ENV + '_build-engine' } as const; }; let _queues: ReturnType | undefined = undefined; diff --git a/src/routes/(api)/system/check/+server.ts b/src/routes/(api)/system/check/+server.ts index 51c4128d..33637f81 100644 --- a/src/routes/(api)/system/check/+server.ts +++ b/src/routes/(api)/system/check/+server.ts @@ -2,5 +2,5 @@ import type { RequestHandler } from './$types'; // GET system/check export const GET: RequestHandler = () => { - return new Response(JSON.stringify({})); + return new Response(JSON.stringify({ versions: {}, imageHash: '' })); }; diff --git a/src/routes/(auth)/+layout.svelte b/src/routes/(auth)/+layout.svelte new file mode 100644 index 00000000..9c52bde4 --- /dev/null +++ b/src/routes/(auth)/+layout.svelte @@ -0,0 +1,16 @@ + + +
    + {@render children?.()} +
    diff --git a/src/routes/(auth)/exchange/+server.ts b/src/routes/(auth)/exchange/+server.ts new file mode 100644 index 00000000..1ef04f6e --- /dev/null +++ b/src/routes/(auth)/exchange/+server.ts @@ -0,0 +1,60 @@ +import { error } from 'console'; +import { EncryptJWT, jwtVerify } from 'jose'; +import type { RequestHandler } from './$types'; +import { env as secrets } from '$env/dynamic/private'; +import { env } from '$env/dynamic/public'; +import { returnTo } from '$lib/server/auth'; +import { QueueConnected } from '$lib/server/bullmq'; +import { getAuthConnection } from '$lib/server/bullmq/queues'; + +// GET system/check +export const GET: RequestHandler = async (event) => { + if (QueueConnected()) { + const requestId = event.url.searchParams.get('requestId'); + const code = event.url.searchParams.get('code'); + if (!requestId || !code) { + throw error(400, 'Missing URL Search Params'); + } + + const verify = await getAuthConnection().get(requestId); + if (!verify) { + throw error(400, 'Invalid or expired code'); + } + + try { + //immediately invalidate + await getAuthConnection().del(requestId); + } catch { + /* empty */ + } + + const res: { id_token?: string } = await fetch( + `${env.PUBLIC_SCRIPTORIA_URL}/api/auth/exchange`, + { + method: 'POST', + body: JSON.stringify({ + code, + verify + }) + } + ).then((r) => r.json()); + + if (!res.id_token) { + throw error(401, 'Authentication failed'); + } + + const key = new TextEncoder().encode(secrets.AUTH0_SECRET); + + const token = await jwtVerify(res.id_token, key); + + const encryptedToken = await new EncryptJWT(token.payload) + .setProtectedHeader({ alg: 'dir', enc: 'A256CBC-HS512' }) + .encrypt(key); + + event.cookies.set('scriptoria.session-token', encryptedToken, { path: '/' }); + + throw returnTo(event); + } else { + throw error(503, 'Service Unavailable'); + } +}; diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts new file mode 100644 index 00000000..1ea6f99b --- /dev/null +++ b/src/routes/(auth)/login/+page.server.ts @@ -0,0 +1,15 @@ +import type { Actions, PageServerLoad } from './$types'; +import { returnTo, tryVerifyCookie } from '$lib/server/auth'; +import { QueueConnected } from '$lib/server/bullmq'; + +export const load: PageServerLoad = async (event) => { + return { + serviceAvailable: QueueConnected() + }; +}; +export const actions: Actions = { + async login(event) { + await tryVerifyCookie(event, false); + throw returnTo(event); + } +}; diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte new file mode 100644 index 00000000..4480c664 --- /dev/null +++ b/src/routes/(auth)/login/+page.svelte @@ -0,0 +1,65 @@ + + +
    +
    +
    + +
    +

    Welcome to BuildEngine

    +
    +

    + BuildEngine serves as an interface between Scriptoria and the AWS resources it uses. +

    + {#if data.serviceAvailable} +
    + +
    + {:else} +

    + BuildEngine is currently unavailable +

    + {/if} +
    +
    + +
    + + Like to use our service? + + + Visit Scriptoria + +
    diff --git a/src/routes/(auth)/signout/+page.server.ts b/src/routes/(auth)/signout/+page.server.ts new file mode 100644 index 00000000..cc21277e --- /dev/null +++ b/src/routes/(auth)/signout/+page.server.ts @@ -0,0 +1,6 @@ +import type { PageServerLoad } from './$types'; +import { invalidateLogin } from '$lib/server/auth'; + +export const load: PageServerLoad = async (event) => { + throw invalidateLogin(event); +}; diff --git a/src/routes/(ui)/+layout.server.ts b/src/routes/(ui)/+layout.server.ts new file mode 100644 index 00000000..e5cf9daa --- /dev/null +++ b/src/routes/(ui)/+layout.server.ts @@ -0,0 +1,5 @@ +import type { LayoutServerLoad } from './$types'; + +export const load = (async ({ locals }) => { + return { userEmail: locals.userEmail }; +}) satisfies LayoutServerLoad; diff --git a/src/routes/(ui)/+layout.svelte b/src/routes/(ui)/+layout.svelte new file mode 100644 index 00000000..7a9e5be5 --- /dev/null +++ b/src/routes/(ui)/+layout.svelte @@ -0,0 +1,81 @@ + + +
    + +
    +
    +
    + {@render children?.()} +
    +
    +
    +
    © SIL Global {new Date().getFullYear()}
    +
    + Powered by  + SvelteKit +
    +
    + + diff --git a/src/routes/+page.svelte b/src/routes/(ui)/+page.svelte similarity index 100% rename from src/routes/+page.svelte rename to src/routes/(ui)/+page.svelte diff --git a/src/routes/about/+page.svelte b/src/routes/(ui)/about/+page.svelte similarity index 100% rename from src/routes/about/+page.svelte rename to src/routes/(ui)/about/+page.svelte diff --git a/src/routes/build-admin/+page.server.ts b/src/routes/(ui)/build-admin/+page.server.ts similarity index 100% rename from src/routes/build-admin/+page.server.ts rename to src/routes/(ui)/build-admin/+page.server.ts diff --git a/src/routes/build-admin/+page.svelte b/src/routes/(ui)/build-admin/+page.svelte similarity index 100% rename from src/routes/build-admin/+page.svelte rename to src/routes/(ui)/build-admin/+page.svelte diff --git a/src/routes/build-admin/update/+page.server.ts b/src/routes/(ui)/build-admin/update/+page.server.ts similarity index 100% rename from src/routes/build-admin/update/+page.server.ts rename to src/routes/(ui)/build-admin/update/+page.server.ts diff --git a/src/routes/build-admin/update/+page.svelte b/src/routes/(ui)/build-admin/update/+page.svelte similarity index 100% rename from src/routes/build-admin/update/+page.svelte rename to src/routes/(ui)/build-admin/update/+page.svelte diff --git a/src/routes/build-admin/view/+page.server.ts b/src/routes/(ui)/build-admin/view/+page.server.ts similarity index 100% rename from src/routes/build-admin/view/+page.server.ts rename to src/routes/(ui)/build-admin/view/+page.server.ts diff --git a/src/routes/build-admin/view/+page.svelte b/src/routes/(ui)/build-admin/view/+page.svelte similarity index 100% rename from src/routes/build-admin/view/+page.svelte rename to src/routes/(ui)/build-admin/view/+page.svelte diff --git a/src/routes/client-admin/+page.server.ts b/src/routes/(ui)/client-admin/+page.server.ts similarity index 100% rename from src/routes/client-admin/+page.server.ts rename to src/routes/(ui)/client-admin/+page.server.ts diff --git a/src/routes/client-admin/+page.svelte b/src/routes/(ui)/client-admin/+page.svelte similarity index 100% rename from src/routes/client-admin/+page.svelte rename to src/routes/(ui)/client-admin/+page.svelte diff --git a/src/routes/client-admin/create/+page.server.ts b/src/routes/(ui)/client-admin/create/+page.server.ts similarity index 100% rename from src/routes/client-admin/create/+page.server.ts rename to src/routes/(ui)/client-admin/create/+page.server.ts diff --git a/src/routes/client-admin/create/+page.svelte b/src/routes/(ui)/client-admin/create/+page.svelte similarity index 100% rename from src/routes/client-admin/create/+page.svelte rename to src/routes/(ui)/client-admin/create/+page.svelte diff --git a/src/routes/client-admin/update/+page.server.ts b/src/routes/(ui)/client-admin/update/+page.server.ts similarity index 100% rename from src/routes/client-admin/update/+page.server.ts rename to src/routes/(ui)/client-admin/update/+page.server.ts diff --git a/src/routes/client-admin/update/+page.svelte b/src/routes/(ui)/client-admin/update/+page.svelte similarity index 100% rename from src/routes/client-admin/update/+page.svelte rename to src/routes/(ui)/client-admin/update/+page.svelte diff --git a/src/routes/client-admin/valibot.ts b/src/routes/(ui)/client-admin/valibot.ts similarity index 100% rename from src/routes/client-admin/valibot.ts rename to src/routes/(ui)/client-admin/valibot.ts diff --git a/src/routes/client-admin/view/+page.server.ts b/src/routes/(ui)/client-admin/view/+page.server.ts similarity index 100% rename from src/routes/client-admin/view/+page.server.ts rename to src/routes/(ui)/client-admin/view/+page.server.ts diff --git a/src/routes/client-admin/view/+page.svelte b/src/routes/(ui)/client-admin/view/+page.svelte similarity index 100% rename from src/routes/client-admin/view/+page.svelte rename to src/routes/(ui)/client-admin/view/+page.svelte diff --git a/src/routes/job-admin/+page.server.ts b/src/routes/(ui)/job-admin/+page.server.ts similarity index 100% rename from src/routes/job-admin/+page.server.ts rename to src/routes/(ui)/job-admin/+page.server.ts diff --git a/src/routes/job-admin/+page.svelte b/src/routes/(ui)/job-admin/+page.svelte similarity index 100% rename from src/routes/job-admin/+page.svelte rename to src/routes/(ui)/job-admin/+page.svelte diff --git a/src/routes/job-admin/update/+page.server.ts b/src/routes/(ui)/job-admin/update/+page.server.ts similarity index 100% rename from src/routes/job-admin/update/+page.server.ts rename to src/routes/(ui)/job-admin/update/+page.server.ts diff --git a/src/routes/job-admin/update/+page.svelte b/src/routes/(ui)/job-admin/update/+page.svelte similarity index 100% rename from src/routes/job-admin/update/+page.svelte rename to src/routes/(ui)/job-admin/update/+page.svelte diff --git a/src/routes/job-admin/view/+page.server.ts b/src/routes/(ui)/job-admin/view/+page.server.ts similarity index 100% rename from src/routes/job-admin/view/+page.server.ts rename to src/routes/(ui)/job-admin/view/+page.server.ts diff --git a/src/routes/job-admin/view/+page.svelte b/src/routes/(ui)/job-admin/view/+page.svelte similarity index 100% rename from src/routes/job-admin/view/+page.svelte rename to src/routes/(ui)/job-admin/view/+page.svelte diff --git a/src/routes/project-admin/+page.server.ts b/src/routes/(ui)/project-admin/+page.server.ts similarity index 100% rename from src/routes/project-admin/+page.server.ts rename to src/routes/(ui)/project-admin/+page.server.ts diff --git a/src/routes/project-admin/+page.svelte b/src/routes/(ui)/project-admin/+page.svelte similarity index 100% rename from src/routes/project-admin/+page.svelte rename to src/routes/(ui)/project-admin/+page.svelte diff --git a/src/routes/project-admin/update/+page.server.ts b/src/routes/(ui)/project-admin/update/+page.server.ts similarity index 100% rename from src/routes/project-admin/update/+page.server.ts rename to src/routes/(ui)/project-admin/update/+page.server.ts diff --git a/src/routes/project-admin/update/+page.svelte b/src/routes/(ui)/project-admin/update/+page.svelte similarity index 100% rename from src/routes/project-admin/update/+page.svelte rename to src/routes/(ui)/project-admin/update/+page.svelte diff --git a/src/routes/project-admin/view/+page.server.ts b/src/routes/(ui)/project-admin/view/+page.server.ts similarity index 100% rename from src/routes/project-admin/view/+page.server.ts rename to src/routes/(ui)/project-admin/view/+page.server.ts diff --git a/src/routes/project-admin/view/+page.svelte b/src/routes/(ui)/project-admin/view/+page.svelte similarity index 100% rename from src/routes/project-admin/view/+page.svelte rename to src/routes/(ui)/project-admin/view/+page.svelte diff --git a/src/routes/queue-admin/[...rest]/+page.svelte b/src/routes/(ui)/queue-admin/[...rest]/+page.svelte similarity index 100% rename from src/routes/queue-admin/[...rest]/+page.svelte rename to src/routes/(ui)/queue-admin/[...rest]/+page.svelte diff --git a/src/routes/release-admin/+page.server.ts b/src/routes/(ui)/release-admin/+page.server.ts similarity index 100% rename from src/routes/release-admin/+page.server.ts rename to src/routes/(ui)/release-admin/+page.server.ts diff --git a/src/routes/release-admin/+page.svelte b/src/routes/(ui)/release-admin/+page.svelte similarity index 100% rename from src/routes/release-admin/+page.svelte rename to src/routes/(ui)/release-admin/+page.svelte diff --git a/src/routes/release-admin/update/+page.server.ts b/src/routes/(ui)/release-admin/update/+page.server.ts similarity index 100% rename from src/routes/release-admin/update/+page.server.ts rename to src/routes/(ui)/release-admin/update/+page.server.ts diff --git a/src/routes/release-admin/update/+page.svelte b/src/routes/(ui)/release-admin/update/+page.svelte similarity index 100% rename from src/routes/release-admin/update/+page.svelte rename to src/routes/(ui)/release-admin/update/+page.svelte diff --git a/src/routes/release-admin/view/+page.server.ts b/src/routes/(ui)/release-admin/view/+page.server.ts similarity index 100% rename from src/routes/release-admin/view/+page.server.ts rename to src/routes/(ui)/release-admin/view/+page.server.ts diff --git a/src/routes/release-admin/view/+page.svelte b/src/routes/(ui)/release-admin/view/+page.svelte similarity index 100% rename from src/routes/release-admin/view/+page.svelte rename to src/routes/(ui)/release-admin/view/+page.svelte diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d80902ff..2546d7fb 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,15 +1,10 @@ @@ -17,37 +12,4 @@ {dev ? '[DEV] ' : ''}{$title || 'SIL AppBuilder Administration'} -
    - -
    -
    -
    - {@render children?.()} -
    -
    -
    -
    © SIL Global {new Date().getFullYear()}
    -
    - Powered by  - SvelteKit -
    -
    - - +{@render children()} \ No newline at end of file From a61708f2fcac1bac479aa2b114b2399523b3d597 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 2 Feb 2026 10:52:39 -0600 Subject: [PATCH 092/144] Allow job update necessary to support https://github.com/sillsdev/appbuilder-portal/pull/1445 --- .../(api)/job/[jobId=idNumber]/+server.ts | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/routes/(api)/job/[jobId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/+server.ts index 72480879..67426c54 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/+server.ts @@ -1,3 +1,4 @@ +import * as v from 'valibot'; import type { RequestHandler } from './$types'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; @@ -31,9 +32,41 @@ export const GET: RequestHandler = async ({ params }) => { ); }; +const updateSchema = v.strictObject({ + publisher_id: v.string() +}); + // PUT /job/[id] -export const PUT: RequestHandler = async () => { - return ErrorResponse(405, 'PUT /job/[id] is not supported at this time', { Allow: 'GET' }); +export const PUT: RequestHandler = async ({ request, params }) => { + const id = Number(params.jobId); + const parsed = v.safeParse(updateSchema, await request.json()); + if (!parsed.success) return ErrorResponse(400, JSON.stringify(v.flatten(parsed.issues))); + const job = await prisma.job.update({ + where: { + id + }, + data: { ...parsed.output }, + select: { + id: true, + request_id: true, + git_url: true, + app_id: true, + publisher_id: true, + created: true, + updated: true + } + }); + return new Response( + JSON.stringify({ + ...job, + client_id: undefined, + _links: { + self: { + href: `${process.env.ORIGIN || 'http://localhost:8443'}/job/${job.id}` + } + } + }) + ); }; // DELETE /job/[id] From 52b2341a4fd6f0d0199a74d6304695e68683d5e2 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 2 Feb 2026 11:08:23 -0600 Subject: [PATCH 093/144] Add content-type header for token exchange --- src/routes/(auth)/exchange/+server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/(auth)/exchange/+server.ts b/src/routes/(auth)/exchange/+server.ts index 1ef04f6e..db3735d9 100644 --- a/src/routes/(auth)/exchange/+server.ts +++ b/src/routes/(auth)/exchange/+server.ts @@ -32,6 +32,7 @@ export const GET: RequestHandler = async (event) => { `${env.PUBLIC_SCRIPTORIA_URL}/api/auth/exchange`, { method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code, verify From 6b6323af70dc3aa8e68530f59d662f9baa9bc67d Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 2 Feb 2026 16:26:07 -0600 Subject: [PATCH 094/144] Add jobs for product release --- src/lib/server/actions/release.ts | 196 ------------------ src/lib/server/bullmq/BullMQ.ts | 2 +- src/lib/server/bullmq/BullWorker.ts | 16 +- src/lib/server/bullmq/queues.ts | 4 +- src/lib/server/bullmq/types.ts | 47 ++--- src/lib/server/job-executors/build.ts | 4 - src/lib/server/job-executors/index.ts | 2 +- src/lib/server/job-executors/polling.ts | 96 ++++++++- src/lib/server/job-executors/publish.ts | 10 - src/lib/server/job-executors/release.ts | 56 +++++ .../build/[buildId=idNumber]/+server.ts | 9 + 11 files changed, 177 insertions(+), 265 deletions(-) delete mode 100644 src/lib/server/actions/release.ts delete mode 100644 src/lib/server/job-executors/publish.ts create mode 100644 src/lib/server/job-executors/release.ts diff --git a/src/lib/server/actions/release.ts b/src/lib/server/actions/release.ts deleted file mode 100644 index 9be70fbc..00000000 --- a/src/lib/server/actions/release.ts +++ /dev/null @@ -1,196 +0,0 @@ -import type { Prisma } from '@prisma/client'; -import { readFile } from 'node:fs/promises'; -import { Build } from '../../models/build'; -import { Release } from '../../models/release'; -import { CodeBuild, type ReleaseForCodeBuild } from '../aws/codebuild'; -import { prisma } from '../prisma'; -import { Utils } from '../utils'; - -export class ManageReleasesAction { - public performAction() { - // for all releases where status is not complete - // if initialized, try start release - // if active, chech status - } - /*=============================================== logging ============================================*/ - /** - * - * get release details for logging + - * @param Release release - * @return Array - */ - public getlogReleaseDetails( - release: Prisma.releaseGetPayload<{ - select: { - id: true; - status: true; - build_guid: true; - result: true; - build: { select: { job: { select: { id: true } } } }; - }; - }> - ) { - const build = release.build; - const job = build.job; - - const jobName = build.job.name(); - const log = { - jobName: jobName, - jobId: job.id, - 'Release-id': release.id, - 'Release-Status': release.status, - 'Release-Build': release.build_guid, - 'Release-Result': release.result - }; - - console.log( - `Release=${release.id}, Build=${release.build_guid}, Status=${release.status}, Result=${release.result}` - ); - - return log; - } - /** - * - * @param Release release - */ - private async tryStartRelease(release: ReleaseForCodeBuild) { - try { - const prefix = Utils.getPrefix(); - console.log( - `[${prefix}] tryStartRelease: Starting Build of ${release.jobName()} for Channel ${release.channel}` - ); - - const script = (await readFile('scripts/appbuilders_publish.yml')).toString(); - - // Start the build - const codeBuild = new CodeBuild(); - const lastBuildGuid = await codeBuild.startRelease(release, script); - if (lastBuildGuid) { - console.log(`[${prefix}] Launched Build LastBuildNumber=${lastBuildGuid}`); - await prisma.release.update({ - where: { id: release.id }, - data: { - build_guid: lastBuildGuid, - codebuild_url: CodeBuild.getCodeBuildUrl('publish_app', lastBuildGuid), - console_text_url: CodeBuild.getConsoleTextUrl('publish_app', lastBuildGuid), - status: Release.Status.Active - } - }); - } - } catch (e) { - console.log(`[${Utils.getPrefix()}] tryStartRelease: Exception:${e}`); - } - } - /** - * @param Release release - */ - private async checkReleaseStatus( - release: Prisma.releaseGetPayload<{ - select: { - id: true; - channel: true; - build_guid: true; - status: true; - result: true; - build: { - select: { - id: true; - channel: true; - job: { - select: { - id: true; - }; - }; - }; - }; - }; - }> - ) { - try { - const prefix = Utils.getPrefix(); - console.log( - `[${prefix}] checkReleaseStatus: Checking Build of ${release.jobName()} for Channel ${release.channel}` - ); - - const build = release.build; - console.log('Build id : ' + build.id); - const job = build.job; - if (job) { - const codeBuild = new CodeBuild(); - - const buildStatus = await codeBuild.getBuildStatus( - release.build_guid!, - CodeBuild.getCodeBuildProjectName('publish_app') - ); - const phase = buildStatus?.currentPhase; - let status = buildStatus?.buildStatus; - console.log(` phase: ${phase} status: ${status}`); - if (codeBuild.isBuildComplete(buildStatus)) { - console.log(' Build Complete'); - } else { - console.log(' Build Incomplete'); - } - - let savedStatus = release.status; - let savedResult = release.result; - - if (codeBuild.isBuildComplete(buildStatus)) { - savedStatus = Release.Status.PostProcessing; - status = codeBuild.getStatus(buildStatus); - switch (status) { - case CodeBuild.Status.Failed: - case CodeBuild.Status.Fault: - case CodeBuild.Status.TimedOut: - savedResult = Build.Result.Failure; - this.handleFailure(release); - break; - case CodeBuild.Status.Stopped: - savedResult = Build.Result.Aborted; - this.handleFailure(release); - break; - case CodeBuild.Status.Succeeded: - savedResult = Build.Result.Success; - await prisma.build.update({ - where: { id: build.id }, - data: { channel: release.channel } - }); - OperationQueue.findOrCreate(OperationQueue.SAVETOS3, release.id, 'release'); - break; - } - } - const saved = await prisma.release.update({ - where: { id: release.id }, - data: { - status: savedStatus, - result: savedResult - } - }); - if (!saved) { - throw new Error( - `Unable to update Build entry, model errors: ${JSON.stringify(release.getFirstErrors(), null, 4)}` - ); - } - } - } catch (e) { - console.log(`[${Utils.getPrefix()}] checkBuildStatus: Exception:${e}`); - this.failRelease(release.id); - } - } - private handleFailure(release: Prisma.releaseGetPayload<{ select: { id: true } }>) { - release.error = release.cloudWatch(); - const release_id = release.id; - const task = OperationQueue.SAVEERRORTOS3; - OperationQueue.findOrCreate(task, release_id, 'release'); - } - - private async failRelease(id: number) { - try { - await prisma.release.update({ - where: { id }, - data: { result: Build.Result.Failure, status: Release.Status.Completed } - }); - } catch (e) { - console.log(`[${Utils.getPrefix()}] failRelease Exception:${e}`); - } - } -} diff --git a/src/lib/server/bullmq/BullMQ.ts b/src/lib/server/bullmq/BullMQ.ts index 74cfc00a..a7f50d3b 100644 --- a/src/lib/server/bullmq/BullMQ.ts +++ b/src/lib/server/bullmq/BullMQ.ts @@ -7,7 +7,7 @@ export const allWorkers = building new Workers.Builds(), new Workers.S3(), new Workers.Projects(), - new Workers.Publishing(), + new Workers.Releases(), new Workers.Polling(), new Workers.SystemStartup() ]; diff --git a/src/lib/server/bullmq/BullWorker.ts b/src/lib/server/bullmq/BullWorker.ts index 0e3c09e6..0afd3d06 100644 --- a/src/lib/server/bullmq/BullWorker.ts +++ b/src/lib/server/bullmq/BullWorker.ts @@ -61,8 +61,6 @@ export class Builds extends BullWorker { switch (job.data.type) { case BullMQ.JobType.Build_Product: return Executor.Build.product(job as Job); - case BullMQ.JobType.Build_PostProcess: - return Executor.Build.postProcess(job as Job); } } } @@ -93,16 +91,14 @@ export class Projects extends BullWorker { } } -export class Publishing extends BullWorker { +export class Releases extends BullWorker { constructor() { - super(BullMQ.QueueName.Publishing); + super(BullMQ.QueueName.Releases); } async run(job: Job) { switch (job.data.type) { - case BullMQ.JobType.Publish_Product: - return Executor.Publish.product(job as Job); - case BullMQ.JobType.Publish_PostProcess: - return Executor.Publish.postProcess(job as Job); + case BullMQ.JobType.Release_Product: + return Executor.Release.product(job as Job); } } } @@ -115,8 +111,8 @@ export class Polling extends BullWorker { switch (job.data.type) { case BullMQ.JobType.Poll_Build: return Executor.Polling.build(job as Job); - case BullMQ.JobType.Poll_Publish: - return Executor.Polling.publish(job as Job); + case BullMQ.JobType.Poll_Release: + return Executor.Polling.release(job as Job); } } } diff --git a/src/lib/server/bullmq/queues.ts b/src/lib/server/bullmq/queues.ts index b38c3b8b..5ff0b392 100644 --- a/src/lib/server/bullmq/queues.ts +++ b/src/lib/server/bullmq/queues.ts @@ -94,7 +94,7 @@ function createQueues() { /** Queue for miscellaneous jobs in BuildEngine such as Product and Project Creation */ const Projects = new Queue(QueueName.Projects, getQueueConfig()); /** Queue for Product Publishing */ - const Publishing = new Queue(QueueName.Publishing, getQueueConfig()); + const Releases = new Queue(QueueName.Releases, getQueueConfig()); /** Queue for jobs that poll BuildEngine, such as checking the status of a build */ const Polling = new Queue(QueueName.Polling, getQueueConfig()); /** Queue for jobs that run on startup, such as creating the CodeBuild project */ @@ -103,7 +103,7 @@ function createQueues() { Builds, S3, Projects, - Publishing, + Releases, Polling, SystemStartup }; diff --git a/src/lib/server/bullmq/types.ts b/src/lib/server/bullmq/types.ts index 324cec60..fe866e47 100644 --- a/src/lib/server/bullmq/types.ts +++ b/src/lib/server/bullmq/types.ts @@ -19,7 +19,7 @@ export enum QueueName { Builds = 'Builds', S3 = 'S3', Projects = 'Projects', - Publishing = 'Publishing', + Releases = 'Releases', Polling = 'Polling', System_Startup = 'System (Startup)' } @@ -27,15 +27,13 @@ export enum QueueName { export enum JobType { // Build Jobs Build_Product = 'Build Product', - Build_PostProcess = 'Postprocess Build', // Polling Jobs Poll_Build = 'Check Product Build', - Poll_Publish = 'Check Product Publish', + Poll_Release = 'Check Product Release', // Project Jobs Project_Create = 'Create Project', // Publishing Jobs - Publish_Product = 'Publish Product', - Publish_PostProcess = 'Postprocess Publish', + Release_Product = 'Release Product', // S3 Jobs S3_CopyArtifacts = 'Copy Artifacts to S3', S3_CopyError = 'Copy Errors to S3', @@ -48,13 +46,6 @@ export namespace Build { type: JobType.Build_Product; buildId: number; } - - export interface PostProcess { - type: JobType.Build_PostProcess; - productId: string; - productBuildId: number; - build: unknown; - } } export namespace Polling { @@ -63,8 +54,8 @@ export namespace Polling { buildId: number; } - export interface Publish { - type: JobType.Poll_Publish; + export interface Release { + type: JobType.Poll_Release; organizationId: number; productId: string; jobId: number; @@ -81,20 +72,10 @@ export namespace Project { } } -export namespace Publish { +export namespace Release { export interface Product { - type: JobType.Publish_Product; - productId: string; - defaultChannel: string; - defaultTargets: string; - environment: Record; - } - - export interface PostProcess { - type: JobType.Publish_PostProcess; - productId: string; - publicationId: number; - release: unknown; + type: JobType.Release_Product; + releaseId: number; } } @@ -119,21 +100,19 @@ export namespace System { export type Job = JobTypeMap[keyof JobTypeMap]; -export type BuildJob = JobTypeMap[JobType.Build_Product | JobType.Build_PostProcess]; +export type BuildJob = JobTypeMap[JobType.Build_Product]; export type S3Job = JobTypeMap[JobType.S3_CopyArtifacts | JobType.S3_CopyError]; -export type PublishJob = JobTypeMap[JobType.Publish_Product | JobType.Publish_PostProcess]; -export type PollJob = JobTypeMap[JobType.Poll_Build | JobType.Poll_Publish]; +export type PublishJob = JobTypeMap[JobType.Release_Product]; +export type PollJob = JobTypeMap[JobType.Poll_Build | JobType.Poll_Release]; export type ProjectJob = JobTypeMap[JobType.Project_Create]; export type SystemJob = JobTypeMap[JobType.System_CreateCodeBuildProject]; export type JobTypeMap = { [JobType.Build_Product]: Build.Product; - [JobType.Build_PostProcess]: Build.PostProcess; [JobType.Poll_Build]: Polling.Build; - [JobType.Poll_Publish]: Polling.Publish; + [JobType.Poll_Release]: Polling.Release; [JobType.Project_Create]: Project.Create; - [JobType.Publish_Product]: Publish.Product; - [JobType.Publish_PostProcess]: Publish.PostProcess; + [JobType.Release_Product]: Release.Product; [JobType.S3_CopyArtifacts]: S3.CopyArtifacts; [JobType.S3_CopyError]: S3.CopyErrors; [JobType.System_CreateCodeBuildProject]: System.CreateCodeBuildProject; diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index ff251f5a..4891368e 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -137,10 +137,6 @@ export async function product(job: Job): Promise } } -export async function postProcess(job: Job): Promise { - return; -} - async function getVersionCode( job: Prisma.jobGetPayload<{ select: { id: true; existing_version_code: true } }> ) { diff --git a/src/lib/server/job-executors/index.ts b/src/lib/server/job-executors/index.ts index a570b46b..d7a7066d 100644 --- a/src/lib/server/job-executors/index.ts +++ b/src/lib/server/job-executors/index.ts @@ -1,6 +1,6 @@ export * as Build from './build'; export * as Polling from './polling'; export * as Project from './project'; -export * as Publish from './publish'; +export * as Release from './release'; export * as S3 from './s3'; export * as System from './system'; diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index a1ec683d..e96e7b84 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -4,6 +4,7 @@ import { Build } from '../../models/build'; import { CodeBuild } from '../aws/codebuild'; import { BullMQ, getQueues } from '../bullmq'; import { prisma } from '../prisma'; +import { Release } from '$lib/models/release'; export async function build(job: Job): Promise { try { @@ -38,11 +39,11 @@ export async function build(job: Job): Promise { case CodeBuild.Status.Fault: case CodeBuild.Status.TimedOut: build.result = Build.Result.Failure; - await handleFailure(build); + await handleBuildFailure(build); break; case CodeBuild.Status.Stopped: build.result = Build.Result.Aborted; - await handleFailure(build); + await handleBuildFailure(build); break; case CodeBuild.Status.Succeeded: await getQueues().S3.add(`Save Build ${job.data.buildId} to S3`, { @@ -79,11 +80,7 @@ export async function build(job: Job): Promise { } } -export async function publish(job: Job): Promise { - return; -} - -async function handleFailure( +async function handleBuildFailure( build: Prisma.buildGetPayload<{ select: { error: true; id: true; console_text_url: true } }> ) { build.error = build.console_text_url; @@ -93,3 +90,88 @@ async function handleFailure( id: build.id }); } + +export async function release(job: Job): Promise { + try { + const release = await prisma.release.findUnique({ + where: { id: job.data.releaseId }, + include: { build: true } + }); + + if (release?.build) { + const codeBuild = new CodeBuild(); + + const buildStatus = await codeBuild.getBuildStatus( + release.build_guid!, + CodeBuild.getCodeBuildProjectName('publish_app') + ); + const phase = buildStatus?.currentPhase; + let status = buildStatus?.buildStatus; + job.log(` phase: ${phase} status: ${status}`); + if (codeBuild.isBuildComplete(buildStatus)) { + job.log(' Build Complete'); + } else { + job.log(' Build Incomplete'); + } + + if (codeBuild.isBuildComplete(buildStatus)) { + release.status = Build.Status.PostProcessing; + status = codeBuild.getStatus(buildStatus); + switch (status) { + case CodeBuild.Status.Failed: + case CodeBuild.Status.Fault: + case CodeBuild.Status.TimedOut: + release.result = Build.Result.Failure; + await handleReleaseFailure(release); + break; + case CodeBuild.Status.Stopped: + release.result = Build.Result.Aborted; + await handleReleaseFailure(release); + break; + case CodeBuild.Status.Succeeded: + release.result = Build.Result.Success; + await prisma.build.update({ + where: { id: release.build.id }, + data: { channel: release.channel } + }); + await getQueues().S3.add(`Save Release ${release.id} to S3`, { + type: BullMQ.JobType.S3_CopyArtifacts, + scope: 'release', + id: release.id + }); + break; + } + } + await prisma.release.update({ + where: { id: release.id }, + data: { + status: release.status, + result: release.result + } + }); + return { + status: release.status + }; + } + } catch (e) { + job.log(`${e}`); + await prisma.release.update({ + where: { id: job.data.releaseId }, + data: { result: Build.Result.Failure, status: Release.Status.Completed } + }); + } +} + +async function handleReleaseFailure( + release: Prisma.releaseGetPayload<{ select: { id: true; console_text_url: true } }> +) { + await prisma.release.update({ + where: { id: release.id }, + data: { error: release.console_text_url } + }); + await getQueues().S3.add(`Save Errors for Release ${release.id} to S3`, { + type: BullMQ.JobType.S3_CopyError, + scope: 'release', + id: release.id + }); +} diff --git a/src/lib/server/job-executors/publish.ts b/src/lib/server/job-executors/publish.ts deleted file mode 100644 index 989e6bb2..00000000 --- a/src/lib/server/job-executors/publish.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Job } from 'bullmq'; -import type { BullMQ } from '../bullmq'; - -export async function product(job: Job): Promise { - return; -} - -export async function postProcess(job: Job): Promise { - return; -} diff --git a/src/lib/server/job-executors/release.ts b/src/lib/server/job-executors/release.ts new file mode 100644 index 00000000..b902e9a4 --- /dev/null +++ b/src/lib/server/job-executors/release.ts @@ -0,0 +1,56 @@ +import type { Job } from 'bullmq'; +import { readFile } from 'fs/promises'; +import { CodeBuild } from '../aws/codebuild'; +import type { BullMQ } from '../bullmq'; +import { prisma } from '../prisma'; +import { Build } from '$lib/models/build'; +import { Release } from '$lib/models/release'; + +export async function product(job: Job): Promise { + try { + const release = await prisma.release.findUniqueOrThrow({ + where: { id: job.data.releaseId }, + include: { + build: { + include: { + job: true + } + } + } + }); + + job.updateProgress(10); + + const script = (await readFile('scripts/appbuilders_publish.yml')).toString(); + + job.updateProgress(30); + // Start the build + const codeBuild = new CodeBuild(); + const lastBuildGuid = await codeBuild.startRelease(release, script); + + job.updateProgress(80); + if (lastBuildGuid) { + await prisma.release.update({ + where: { id: job.data.releaseId }, + data: { + build_guid: lastBuildGuid, + codebuild_url: CodeBuild.getCodeBuildUrl('publish_app', lastBuildGuid), + console_text_url: CodeBuild.getConsoleTextUrl('publish_app', lastBuildGuid), + status: Release.Status.Active + } + }); + } + job.updateProgress(100); + return { lastBuildGuid }; + } catch (e) { + job.log(`${e}`); + await prisma.release.update({ + where: { id: job.data.releaseId }, + data: { + result: Build.Result.Failure, + status: Release.Status.Completed, + error: String(e) + } + }); + } +} diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts index 95410bf8..0103c498 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts @@ -2,6 +2,7 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; import { Build } from '$lib/models/build'; import { Release } from '$lib/models/release'; +import { BullMQ, getQueues } from '$lib/server/bullmq'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; @@ -108,6 +109,14 @@ export const PUT: RequestHandler = async ({ request, params }) => { } }); + await getQueues().Releases.add( + `Start Release #${release.id} for Build ${build.id} of Job ${job.id}`, + { + type: BullMQ.JobType.Release_Product, + releaseId: release.id + } + ); + return new Response( JSON.stringify({ ...release, From 3439e49dd0fb9626c0e6ae57a226bb5b78bd0df6 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Mon, 2 Feb 2026 16:36:08 -0600 Subject: [PATCH 095/144] Finish translating project action --- src/lib/models/project.ts | 11 +++++++++++ src/lib/server/actions/project.ts | 28 ++++++++++------------------ 2 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 src/lib/models/project.ts diff --git a/src/lib/models/project.ts b/src/lib/models/project.ts new file mode 100644 index 00000000..ef913b72 --- /dev/null +++ b/src/lib/models/project.ts @@ -0,0 +1,11 @@ +import type { Prisma } from '@prisma/client'; + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Project { + export type ProjectGroup = Prisma.projectGetPayload<{ + select: { group_id: true; client: { select: { prefix: true } } }; + }>; + export function groupName(project: ProjectGroup) { + return `CodeCommit-${project.client ? project.client.prefix + '-' : ''}-${project.group_id?.toUpperCase() ?? ''}`; + } +} diff --git a/src/lib/server/actions/project.ts b/src/lib/server/actions/project.ts index d7660207..7bf686b4 100644 --- a/src/lib/server/actions/project.ts +++ b/src/lib/server/actions/project.ts @@ -2,13 +2,11 @@ import type { Prisma } from '@prisma/client'; import { IAmWrapper } from '../aws/iamwrapper'; import { prisma } from '../prisma'; import { Utils } from '../utils'; +import { Project } from '$lib/models/project'; export class ProjectUpdateOperation { private id; private parms; - private maxRetries = 50; - private maxDelay = 30; - private alertAfter = 5; public iAmWrapper; // Public for ut public constructor(id: number, parms: string) { @@ -18,7 +16,10 @@ export class ProjectUpdateOperation { } public async performOperation() { console.log(`[${Utils.getPrefix()}] ProjectUpdateOperation ID: ${this.id}`); - const project = await prisma.project.findUnique({ where: { id: this.id } }); + const project = await prisma.project.findUnique({ + where: { id: this.id }, + include: { client: true } + }); if (project) { console.log('Found record'); const parmsArray = this.parms.split(','); @@ -30,15 +31,6 @@ export class ProjectUpdateOperation { console.log("Didn't find record"); } } - public getMaximumRetries() { - return this.maxRetries; - } - public getMaximumDelay() { - return this.maxDelay; - } - public getAlertAfterAttemptCount() { - return this.alertAfter; - } /** * If the user/group combination associated with the current project is * the only project that exists, then remove the IAM user from the IAM group @@ -47,7 +39,7 @@ export class ProjectUpdateOperation { * @return void */ private async checkRemoveUserFromGroup( - project: Prisma.projectGetPayload<{ select: { user_id: true; group_id: true } }> + project: Prisma.projectGetPayload<{ select: { user_id: true } }> & Project.ProjectGroup ) { console.log('checkRemoveUserFromGroup'); const projects = await prisma.project.count({ @@ -56,19 +48,19 @@ export class ProjectUpdateOperation { if (projects < 2) { // Remove the user from the group console.log( - `CheckRemoveUserFromGroup: Removing [${project.user_id}] from group [${project.groupName()}]` + `CheckRemoveUserFromGroup: Removing [${project.user_id}] from group [${Project.groupName(project)}]` ); - this.iAmWrapper.removeUserFromIamGroup(project.user_id!, project.groupName()); + this.iAmWrapper.removeUserFromIamGroup(project.user_id!, Project.groupName(project)); } } private async updateProject( - project: Prisma.projectGetPayload<{ select: { id: true; url: true } }>, + project: Prisma.projectGetPayload<{ select: { id: true; url: true } }> & Project.ProjectGroup, user_id: string, publishing_key: string ) { console.log('updateProject'); await this.iAmWrapper.createAwsAccount(user_id); - this.iAmWrapper.addUserToIamGroup(user_id, project.groupName()); + this.iAmWrapper.addUserToIamGroup(user_id, Project.groupName(project)); const public_key = await this.iAmWrapper.addPublicSshKey(user_id, publishing_key); const publicKeyId = public_key?.SSHPublicKey?.SSHPublicKeyId; const url = this.adjustUrl(project.url!, publicKeyId!); From 1ffb42cb5f78538f01fc45bd874d179e92151114 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Tue, 21 Oct 2025 13:38:19 -0500 Subject: [PATCH 096/144] Write APPBUILDER_SCRIPT_VERSION to version.json (#63) --- scripts/upload/default/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index a3d225b0..3ec77239 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -635,9 +635,9 @@ complete_successful_build() { fi if [[ "${VERSION_CODE}" != "" ]]; then echo "${VERSION_CODE}" > "$OUTPUT_DIR"/version_code.txt - echo "{ \"version\" : \"${VERSION_NAME} (${VERSION_CODE})\", \"versionName\" : \"${VERSION_NAME}\", \"versionCode\" : \"${VERSION_CODE}\" } " > "$OUTPUT_DIR"/version.json + echo "{ \"version\" : \"${VERSION_NAME} (${VERSION_CODE})\", \"versionName\" : \"${VERSION_NAME}\", \"versionCode\" : \"${VERSION_CODE}\", \"appbuilderVersion\": \"${APPBUILDER_SCRIPT_VERSION}\" } " > "$OUTPUT_DIR"/version.json else - echo "{ \"version\" : \"${VERSION_NAME}\", \"versionName\" : \"${VERSION_NAME}\" } " > "$OUTPUT_DIR"/version.json + echo "{ \"version\" : \"${VERSION_NAME}\", \"versionName\" : \"${VERSION_NAME}\", \"appbuilderVersion\": \"${APPBUILDER_SCRIPT_VERSION}\" } " > "$OUTPUT_DIR"/version.json fi echo "ls -lR ${OUTPUT_DIR}" From 6b1640c60e5004c2729115b00a40ad4cb1fee561 Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Wed, 22 Oct 2025 19:18:27 -0400 Subject: [PATCH 097/144] Fix usage of UI_URL/ORIGIN * Fixes #64: only use PUBLISH_NOTIFY on production * Fixes #65: use UI_URL or ORIGIN --- scripts/upload/default/publish.sh | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/upload/default/publish.sh b/scripts/upload/default/publish.sh index 86f5d312..2b787690 100644 --- a/scripts/upload/default/publish.sh +++ b/scripts/upload/default/publish.sh @@ -5,6 +5,9 @@ set -x LOG_FILE="${OUTPUT_DIR}"/console.log exec > >(tee "${LOG_FILE}") 2>&1 +# Scriptoria 1 uses UI_URL and Scriptoria 2 uses ORIGIN +export SERVER_URL="${ORIGIN:-${UI_URL:-}}" + export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" sync_secrets() { @@ -105,7 +108,7 @@ publish_google_play() { PERMALINK_URL="" else PUBLISH_SIZE="$(stat --format="%s" "${APK_FILES[0]}")" - PERMALINK_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/apk" + PERMALINK_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/apk" fi PUBLISH_URL="https://play.google.com/store/apps/details?id=${PACKAGE_NAME}" echo "${PUBLISH_URL}" > "${OUTPUT_DIR}/publish_url.txt" @@ -138,13 +141,13 @@ publish_s3_bucket() { # apk: publish all the apks PUBLISH_S3_INCLUDE="*.apk" PUBLISH_S3_SOURCH_PATH="${ARTIFACTS_DIR}" - PERMALINK_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/apk" + PERMALINK_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/apk" SRC_FILE="${APK_FILES[0]}" elif [[ "${#ZIP_FILES[@]}" -gt 0 ]]; then # asset-package: publish all the zip files PUBLISH_S3_INCLUDE="*.zip" PUBLISH_S3_SOURCH_PATH="${ARTIFACTS_DIR}/asset-package" - PERMALINK_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/asset-package" + PERMALINK_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/asset-package" SRC_FILE="${ZIP_FILES[0]}" fi PUBLISH_SIZE="$(stat --format="%s" "${SRC_FILE}")" @@ -403,6 +406,14 @@ publish_base_update_json() { '. + { project_url: $project_url, project_name: $project_name, product_name: $product_name, project_language: $project_language, project_repo: $project_repo, publish_url: $publish_url, permalink_url: $permalink_url, size: $size, app_builder: $app_builder, app_builder_version: $app_builder_version }' } +is_production() { + # Return success if SERVER_URL contains the production domain + if [[ -n "${SERVER_URL}" && "${SERVER_URL}" == *"app.scriptoria.io"* ]]; then + return 0 + fi + return 1 +} + post_publish() { PUBLISH_JSON="$(publish_base_update_json "${PUBLISH_JSON}")" @@ -410,7 +421,7 @@ post_publish() { # contain the keywords: ios, android, google (play), and pwa. So the system admin # should be aware when adding new products. WORKFLOW_PRODUCT_NAME_LOWER=$(echo "${WORKFLOW_PRODUCT_NAME}" | awk '{print tolower($0)}') - if [[ "${PUBLISH_NOTIFY}" != "" ]]; then + if is_production && [[ "${PUBLISH_NOTIFY}" != "" ]]; then # See S1: Services/BuildEngine: BuildEngineServiceBase::AddProductProperitiesToEnvironment # See S2: node-server/job-executors/common.build-publish.ts: addProductPropertiesToEnvironment # for the list of properties that are added. @@ -426,12 +437,12 @@ post_publish() { NOTIFY_TYPE="ios" # change protocol to "asset://" so that container app will recognize as asset-package # shellcheck disable=SC2001 - NOTIFY_URL="$(sed -e 's/https*:/asset:/' <<< "$UI_URL")/api/products/${PRODUCT_ID}/files/published/asset-package" + NOTIFY_URL="$(sed -e 's/https*:/asset:/' <<< "$SERVER_URL")/api/products/${PRODUCT_ID}/files/published/asset-package" NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" elif [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"android"* ]]; then # Send Notification for Android app to Google Play NOTIFY_TYPE="apk" - NOTIFY_URL="${UI_URL}/api/products/${PRODUCT_ID}/files/published/apk" + NOTIFY_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/apk" NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" if [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"google"* ]]; then # Send additional notification for Android app to Google Play From 0f4c39c0aee8c81f0a5837c13fb1ba9f1973483f Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Tue, 28 Oct 2025 14:03:11 -0400 Subject: [PATCH 098/144] Download Play Listing (#71) --- scripts/upload/default/build.sh | 34 +++++++++++++++++++++++++++++---- src/lib/models/build.ts | 6 +++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index 3ec77239..2d920198 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -12,14 +12,22 @@ BUILD_DIR=/tmp/build mkdir -p "$BUILD_DIR" SCRIPT_OPT="-fp build=${BUILD_DIR}" -sync_secrets() { +sync_build_secrets() { SECRETS_SUBDIR=$1 SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build" - echo "sync secrets" + echo "sync build secrets" echo "SECRETS_SUBDIR = ${SECRETS_SUBDIR}" aws s3 sync "${SECRETS_S3}/${SECRETS_SUBDIR}" "${SECRETS_DIR}" } +sync_publish_secrets() { + SECRETS_SUBDIR=$1 + SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/publish" + echo "sync publish secrets" + echo "SECRETS_SUBDIR = ${SECRETS_SUBDIR}" + aws s3 sync "${SECRETS_S3}/${SECRETS_SUBDIR}" "${SECRETS_DIR}" +} + check_audio_sources() { if [[ "${BUILD_AUDIO_UPDATE}" == "1" ]]; then if [[ "${AUDIO_UPDATE_SOURCE}" != "" ]]; then @@ -122,12 +130,12 @@ build_apk() { if [[ "${BUILD_KEYSTORE}" != "" ]]; then echo "Using build keystore=${BUILD_KEYSTORE}" SECRETS_SUBDIR="google_play_store/${PUBLISHER}/${BUILD_KEYSTORE}" - sync_secrets "${SECRETS_SUBDIR}" + sync_build_secrets "${SECRETS_SUBDIR}" KS="${SECRETS_DIR}/${BUILD_KEYSTORE}.keystore" else echo "Using publisher keystore=${PUBLISHER}" SECRETS_SUBDIR="google_play_store/${PUBLISHER}" - sync_secrets "${SECRETS_SUBDIR}" + sync_build_secrets "${SECRETS_SUBDIR}" KS="${SECRETS_DIR}/${PUBLISHER}.keystore" fi KSP="$(cat "${SECRETS_DIR}/ksp.txt")" @@ -414,6 +422,22 @@ EOL VERSION_CODE="" } +download_play_listing() { + # For existing apps being added to Scriptoria, we may need to download the Play Store listing information + if [[ "${BUILD_DOWNLOAD_PLAY_LISTING}" == "1" ]]; then + if [[ "${PUBLISHER}" != "" && "${APPDEF_PACKAGE_NAME}" != "" ]]; then + echo "Downloading existing play listing for Publisher:${PUBLISHER}, Package Name:${APPDEF_PACKAGE_NAME}" + SECRETS_SUBDIR="google_play_store/${PUBLISHER}" + sync_publish_secrets "${SECRETS_SUBDIR}" + export SUPPLY_JSON_KEY="${SECRETS_DIR}/playstore_api.json" + export SUPPLY_PACKAGE_NAME="${APPDEF_PACKAGE_NAME}" + export SUPPLY_METADATA_PATH="build_data/publish/play-listing" + fastlane supply init + (cd "${SUPPLY_METADATA_PATH}" && zip -r "${OUTPUT_DIR}/play-listing.zip" .) + fi + fi +} + build_play_listing() { echo "Build play listing" echo "BUILD_NUMBER=${BUILD_NUMBER}" @@ -422,6 +446,8 @@ build_play_listing() { echo "OUTPUT_DIR=${OUTPUT_DIR}" cd "$PROJECT_DIR" || exit 1 + download_play_listing + APK_FILES=("${OUTPUT_DIR}"/*.apk) AAPT="$(find /opt/android-sdk/build-tools -name aapt | head -n 1)" diff --git a/src/lib/models/build.ts b/src/lib/models/build.ts index 613ca100..5a1239a5 100644 --- a/src/lib/models/build.ts +++ b/src/lib/models/build.ts @@ -46,7 +46,8 @@ export namespace Build { AssetPackage = 'asset-package', AssetPreview = 'asset-preview', AssetNotify = 'asset-notify', - DataSafetyCsv = 'data-safety-csv' + DataSafetyCsv = 'data-safety-csv', + PlayListingDownload = 'play-listing-download' } export function artifactType(key: string): [Artifact, string] { @@ -96,6 +97,8 @@ export namespace Build { file = 'play-listing/manifest.json'; } else if (key.match(/data_safety\.csv/)) { type = Artifact.DataSafetyCsv; + } else if (key.match(/play-listing\.zip$/)) { + type = Artifact.PlayListingDownload; } return [type, file]; @@ -153,6 +156,7 @@ export namespace Build { files ); artifacts[Artifact.WhatsNew] = getArtifactUrl(/whats_new\.txt/, base, files); + artifacts[Artifact.PlayListingDownload] = getArtifactUrl(/play-listing\.zip$/, base, files); } if (targets?.match('play-html')) { From b0790ab3211a51e638671d22629818d3d4a02181 Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Sat, 8 Nov 2025 20:35:24 -0500 Subject: [PATCH 099/144] Make sure metadata folder is empty --- scripts/upload/default/build.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index 2d920198..38bbc82c 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -431,8 +431,17 @@ download_play_listing() { sync_publish_secrets "${SECRETS_SUBDIR}" export SUPPLY_JSON_KEY="${SECRETS_DIR}/playstore_api.json" export SUPPLY_PACKAGE_NAME="${APPDEF_PACKAGE_NAME}" - export SUPPLY_METADATA_PATH="build_data/publish/play-listing" + DOWNLOAD_TMP_DIR=$(mktemp -d) + DOWNLOAD_TARGET_DIR="build_data/publish/play-listing" + export SUPPLY_METADATA_PATH="${DOWNLOAD_TMP_DIR}/metadata" + fastlane supply init + + # Ensure metadata dir exists and remove any default files/dirs AppBuilder may have created. + mkdir -p "${DOWNLOAD_TARGET_DIR}" + rm -rf -- "${DOWNLOAD_TARGET_DIR:?}"/* + cp -a "${SUPPLY_METADATA_PATH}/." "${DOWNLOAD_TARGET_DIR}/" + (cd "${SUPPLY_METADATA_PATH}" && zip -r "${OUTPUT_DIR}/play-listing.zip" .) fi fi From 2d5bdf54d623db7996e3b831c1f0352f47c8b7f5 Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Tue, 11 Nov 2025 17:12:26 -0500 Subject: [PATCH 100/144] Update /system/check to return app versions Remove caching of version info --- package-lock.json | 1204 ++++++++++++++++++---- package.json | 1 + src/lib/server/aws/common.ts | 6 +- src/routes/(api)/system/check/+server.ts | 386 ++++++- 4 files changed, 1417 insertions(+), 180 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef4d23e7..862604e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@aws-sdk/client-codebuild": "^3.907.0", "@aws-sdk/client-codecommit": "^3.910.0", + "@aws-sdk/client-ecr": "^3.981.0", "@aws-sdk/client-iam": "^3.911.0", "@aws-sdk/client-s3": "^3.907.0", "@aws-sdk/client-sts": "^3.907.0", @@ -914,6 +915,575 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@aws-sdk/client-ecr": { + "version": "3.981.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecr/-/client-ecr-3.981.0.tgz", + "integrity": "sha512-qdJAq+18gg0vS8K/SDUBC2HHkApURCvrEZz7d7qxXLaUZXrT0bZqR8O+QtqvHWIXdaEME3SM8a8KNZSdqJ0s4A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-node": "^3.972.4", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.981.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sso": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz", + "integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/core": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz", + "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.2", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz", + "integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz", + "integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz", + "integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-env": "^3.972.3", + "@aws-sdk/credential-provider-http": "^3.972.5", + "@aws-sdk/credential-provider-login": "^3.972.3", + "@aws-sdk/credential-provider-process": "^3.972.3", + "@aws-sdk/credential-provider-sso": "^3.972.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz", + "integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.3", + "@aws-sdk/credential-provider-http": "^3.972.5", + "@aws-sdk/credential-provider-ini": "^3.972.3", + "@aws-sdk/credential-provider-process": "^3.972.3", + "@aws-sdk/credential-provider-sso": "^3.972.3", + "@aws-sdk/credential-provider-web-identity": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz", + "integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz", + "integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.980.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/token-providers": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz", + "integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz", + "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@smithy/core": "^3.22.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/nested-clients": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz", + "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/token-providers": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz", + "integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/types": { + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-endpoints": { + "version": "3.981.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.981.0.tgz", + "integrity": "sha512-a8nXh/H3/4j+sxhZk+N3acSDlgwTVSZbX9i55dx41gI1H+geuonuRG+Shv3GZsCb46vzc08RK2qC78ypO8uRlg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz", + "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.3.tgz", + "integrity": "sha512-bCk63RsBNCWW4tt5atv5Sbrh+3J3e8YzgyF6aZb1JeXcdzG4k5SlPLeTMFOIXFuuFHIwgphUhn4i3uS/q49eww==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/fast-xml-parser": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@aws-sdk/client-iam": { "version": "3.911.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.911.0.tgz", @@ -1702,6 +2272,289 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz", + "integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/nested-clients": "3.980.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/core": { + "version": "3.973.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz", + "integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.2", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz", + "integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@smithy/core": "^3.22.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz", + "integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/types": { + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz", + "integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.3.tgz", + "integrity": "sha512-bCk63RsBNCWW4tt5atv5Sbrh+3J3e8YzgyF6aZb1JeXcdzG4k5SlPLeTMFOIXFuuFHIwgphUhn4i3uS/q49eww==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/fast-xml-parser": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@aws-sdk/credential-provider-node": { "version": "3.907.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.907.0.tgz", @@ -3851,12 +4704,12 @@ "optional": true }, "node_modules/@smithy/abort-controller": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz", - "integrity": "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -3907,15 +4760,16 @@ "license": "0BSD" }, "node_modules/@smithy/config-resolver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.2.tgz", - "integrity": "sha512-F/G+VaulIebINyfvcoXmODgIc7JU/lxWK9/iI0Divxyvd2QWB7/ZcF7JKwMssWI6/zZzlMkq/Pt6ow2AOEebPw==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.2", - "@smithy/types": "^4.7.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.2", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -3929,18 +4783,18 @@ "license": "0BSD" }, "node_modules/@smithy/core": { - "version": "3.16.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.16.1.tgz", - "integrity": "sha512-yRx5ag3xEQ/yGvyo80FVukS7ZkeUP49Vbzg0MjfHLkuCIgg5lFtaEJfZR178KJmjWPqLU4d0P4k7SKgF9UkOaQ==", + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", + "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.2", - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.2", - "@smithy/util-stream": "^4.5.2", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -3956,15 +4810,15 @@ "license": "0BSD" }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.2.tgz", - "integrity": "sha512-hOjFTK+4mfehDnfjNkPqHUKBKR2qmlix5gy7YzruNbTdeoBE3QkfNCPvuCK2r05VUJ02QQ9bz2G41CxhSexsMw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.2", - "@smithy/property-provider": "^4.2.2", - "@smithy/types": "^4.7.1", - "@smithy/url-parser": "^4.2.2", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -4078,14 +4932,14 @@ "license": "0BSD" }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.3.tgz", - "integrity": "sha512-cipIcM3xQ5NdIVwcRb37LaQwIxZNMEZb/ZOPmLFS9uGo9TGx2dGCyMBj9oT7ypH4TUD/kOTc/qHmwQzthrSk+g==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.2", - "@smithy/querystring-builder": "^4.2.2", - "@smithy/types": "^4.7.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -4121,12 +4975,12 @@ "license": "0BSD" }, "node_modules/@smithy/hash-node": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.2.tgz", - "integrity": "sha512-xuOPGrF2GUP+9og5NU02fplRVjJjMhAaY8ZconB3eLKjv/VSV9/s+sFf72MYO5Q2jcSRVk/ywZHpyGbE3FYnFQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -4162,12 +5016,12 @@ "license": "0BSD" }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.2.tgz", - "integrity": "sha512-Z0844Zpoid5L1DmKX2+cn2Qu9i3XWjhzwYBRJEWrKJwjUuhEkzf37jKPj9dYFsZeKsAbS2qI0JyLsYafbXJvpA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4219,13 +5073,13 @@ "license": "0BSD" }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.2.tgz", - "integrity": "sha512-aJ7LAuIXStF6EqzRVX9kAW+6/sYoJJv0QqoFrz2BhA9r/85kLYOJ6Ph47wYSGBxzSLxsYT5jqgMw/qpbv1+m+w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4239,18 +5093,18 @@ "license": "0BSD" }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.3.tgz", - "integrity": "sha512-CfxQ6X9L87/3C67Po6AGWXsx8iS4w2BO8vQEZJD6hwqg2vNRC/lMa2O5wXYCG9tKotdZ0R8KG33TS7kpUnYKiw==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", + "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.16.1", - "@smithy/middleware-serde": "^4.2.2", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", - "@smithy/url-parser": "^4.2.2", - "@smithy/util-middleware": "^4.2.2", + "@smithy/core": "^3.22.1", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -4264,18 +5118,18 @@ "license": "0BSD" }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.3.tgz", - "integrity": "sha512-EHnKGeFuzbmER4oSl/VJDxPLi+aiZUb3nk5KK8eNwHjMhI04jHlui2ZkaBzMfNmXOgymaS6zV//fyt6PSnI1ow==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.2", - "@smithy/protocol-http": "^5.3.2", - "@smithy/service-error-classification": "^4.2.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", - "@smithy/util-middleware": "^4.2.2", - "@smithy/util-retry": "^4.2.2", + "version": "4.4.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", + "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, @@ -4290,13 +5144,13 @@ "license": "0BSD" }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.2.tgz", - "integrity": "sha512-tDMPMBCsA1GBxanShhPvQYwdiau3NmctUp+eELMhUTDua+EUrugXlaKCnTMMoEB5mbHFebdv81uJPkVP02oihA==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4310,12 +5164,12 @@ "license": "0BSD" }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.2.tgz", - "integrity": "sha512-7rgzDyLOQouh1bC6gOXnCGSX2dqvbOclgClsFkj735xQM2CHV63Ams8odNZGJgcqnBsEz44V/pDGHU6ALEUD+w==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4329,14 +5183,14 @@ "license": "0BSD" }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.2.tgz", - "integrity": "sha512-u38G0Audi2ORsL0QnzhopZ3yweMblQf8CZNbzUJ3wfTtZ7OiOwOzee0Nge/3dKeG/8lx0kt8K0kqDi6sYu0oKQ==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4350,15 +5204,15 @@ "license": "0BSD" }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.1.tgz", - "integrity": "sha512-9gKJoL45MNyOCGTG082nmx0A6KrbLVQ+5QSSKyzRi0AzL0R81u3wC1+nPvKXgTaBdAKM73fFPdCBHpmtipQwdQ==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", + "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.2", - "@smithy/protocol-http": "^5.3.2", - "@smithy/querystring-builder": "^4.2.2", - "@smithy/types": "^4.7.1", + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4372,12 +5226,12 @@ "license": "0BSD" }, "node_modules/@smithy/property-provider": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.2.tgz", - "integrity": "sha512-MW7MfI+qYe/Ue5RH0uEztEKB+vBlOMM+1Dz68qzTsY8fC9kanXMFPEVdiq35JTGKWt5wZAjU1R0uXYEjK2MM1g==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4391,12 +5245,12 @@ "license": "0BSD" }, "node_modules/@smithy/protocol-http": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.2.tgz", - "integrity": "sha512-nkKOI8xEkBXUmdxsFExomOb+wkU+Xgn0Fq2LMC7YIX5r4YPUg7PLayV/s/u3AtbyjWYlrvN7nAiDTLlqSdUjHw==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4410,12 +5264,12 @@ "license": "0BSD" }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.2.tgz", - "integrity": "sha512-YgXvq89o+R/8zIoeuXYv8Ysrbwgjx+iVYu9QbseqZjMDAhIg/FRt7jis0KASYFtd/Cnsnz4/nYTJXkJDWe8wHg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, @@ -4430,12 +5284,12 @@ "license": "0BSD" }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.2.tgz", - "integrity": "sha512-DczOD2yJy3NXcv1JvhjFC7bIb/tay6nnIRD/qrzBaju5lrkVBOwCT3Ps37tra20wy8PicZpworStK7ZcI9pCRQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4449,24 +5303,24 @@ "license": "0BSD" }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.2.tgz", - "integrity": "sha512-1X17cMLwe/vb4RpZbQVpJ1xQQ7fhQKggMdt3qjdV3+6QNllzvUXyS3WFnyaFWLyaGqfYHKkNONbO1fBCMQyZtQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1" + "@smithy/types": "^4.12.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.2.tgz", - "integrity": "sha512-AWnLgSmOTdDXM8aZCN4Im0X07M3GGffeL9vGfea4mdKZD0cPT9yLF9SsRbEa00tHLI+KfubDrmjpaKT2pM4GdQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4480,16 +5334,16 @@ "license": "0BSD" }, "node_modules/@smithy/signature-v4": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.2.tgz", - "integrity": "sha512-BRnQGGyaRSSL0KtjjFF9YoSSg8qzSqHMub4H2iKkd+LZNzZ1b7H5amslZBzi+AnvuwPMyeiNv0oqay/VmIuoRA==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.2", + "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -4505,17 +5359,17 @@ "license": "0BSD" }, "node_modules/@smithy/smithy-client": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.8.1.tgz", - "integrity": "sha512-N5wK57pVThzLVK5NgmHxocTy5auqGDGQ+JsL5RjCTriPt8JLYgXT0Awa915zCpzc9hXHDOKqDX5g9BFdwkSfUA==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", + "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.16.1", - "@smithy/middleware-endpoint": "^4.3.3", - "@smithy/middleware-stack": "^4.2.2", - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", - "@smithy/util-stream": "^4.5.2", + "@smithy/core": "^3.22.1", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" }, "engines": { @@ -4529,9 +5383,9 @@ "license": "0BSD" }, "node_modules/@smithy/types": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.0.tgz", - "integrity": "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4547,13 +5401,13 @@ "license": "0BSD" }, "node_modules/@smithy/url-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.2.tgz", - "integrity": "sha512-s2EYKukaswzjiHJCss6asB1F4zjRc0E/MFyceAKzb3+wqKA2Z/+Gfhb5FP8xVVRHBAvBkregaQAydifgbnUlCw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.2", - "@smithy/types": "^4.7.1", + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4660,14 +5514,14 @@ "license": "0BSD" }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.2.tgz", - "integrity": "sha512-6JvKHZ5GORYkEZ2+yJKEHp6dQQKng+P/Mu3g3CDy0fRLQgXEO8be+FLrBGGb4kB9lCW6wcQDkN7kRiGkkVAXgg==", + "version": "4.3.29", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", + "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4681,17 +5535,17 @@ "license": "0BSD" }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.3.tgz", - "integrity": "sha512-bkTGuMmKvghfCh9NayADrQcjngoF8P+XTgID5r3rm+8LphFiuM6ERqpBS95YyVaLjDetnKus9zK/bGlkQOOtNQ==", + "version": "4.2.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", + "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.3.2", - "@smithy/credential-provider-imds": "^4.2.2", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/property-provider": "^4.2.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4705,13 +5559,13 @@ "license": "0BSD" }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.2.tgz", - "integrity": "sha512-ZQi6fFTMBkfwwSPAlcGzArmNILz33QH99CL8jDfVWrzwVVcZc56Mge10jGk0zdRgWPXyL1/OXKjfw4vT5VtRQg==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.2", - "@smithy/types": "^4.7.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4743,12 +5597,12 @@ "license": "0BSD" }, "node_modules/@smithy/util-middleware": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.2.tgz", - "integrity": "sha512-wL9tZwWKy0x0qf6ffN7tX5CT03hb1e7XpjdepaKfKcPcyn5+jHAWPqivhF1Sw/T5DYi9wGcxsX8Lu07MOp2Puw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.7.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4762,13 +5616,13 @@ "license": "0BSD" }, "node_modules/@smithy/util-retry": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.2.tgz", - "integrity": "sha512-TlbnWAOoCuG2PgY0Hi3BGU1w2IXs3xDsD4E8WDfKRZUn2qx3wRA9mbYnmpWHPswTJCz2L+ebh+9OvD42sV4mNw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.2", - "@smithy/types": "^4.7.1", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -4782,14 +5636,14 @@ "license": "0BSD" }, "node_modules/@smithy/util-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.2.tgz", - "integrity": "sha512-RWYVuQVKtNbr7E0IxV8XHDId714yHPTxU6dHScd6wSMWAXboErzTG7+xqcL+K3r0Xg0cZSlfuNhl1J0rzMLSSw==", + "version": "4.5.11", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", + "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.3", - "@smithy/node-http-handler": "^4.4.1", - "@smithy/types": "^4.7.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", @@ -4844,13 +5698,13 @@ "license": "0BSD" }, "node_modules/@smithy/util-waiter": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.3.tgz", - "integrity": "sha512-5+nU///E5sAdD7t3hs4uwvCTWQtTR8JwKwOCSJtBRx0bY1isDo1QwH87vRK86vlFLBTISqoDA2V6xvP6nF1isQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", + "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.3", - "@smithy/types": "^4.8.0", + "@smithy/abort-controller": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { diff --git a/package.json b/package.json index 26c0fedf..988082bc 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "dependencies": { "@aws-sdk/client-codebuild": "^3.907.0", "@aws-sdk/client-codecommit": "^3.910.0", + "@aws-sdk/client-ecr": "^3.981.0", "@aws-sdk/client-iam": "^3.911.0", "@aws-sdk/client-s3": "^3.907.0", "@aws-sdk/client-sts": "^3.907.0", diff --git a/src/lib/server/aws/common.ts b/src/lib/server/aws/common.ts index 93737807..dd896c20 100644 --- a/src/lib/server/aws/common.ts +++ b/src/lib/server/aws/common.ts @@ -2,7 +2,7 @@ import { env } from '$env/dynamic/private'; export class AWSCommon { public static getArtifactsBucketRegion() { - return env.BUILD_ENGINE_ARTIFACTS_BUCKET_REGION; + return env.BUILD_ENGINE_ARTIFACTS_BUCKET_REGION || 'us-east-1'; } public static getAwsRegion() { @@ -30,10 +30,10 @@ export class AWSCommon { } public static getCodeBuildImageTag() { - return env.CODE_BUILD_IMAGE_TAG; + return env.CODE_BUILD_IMAGE_TAG || 'production'; } public static getCodeBuildImageRepo() { - return env.CODE_BUILD_IMAGE_REPO; + return env.CODE_BUILD_IMAGE_REPO || 'sillsdev/appbuilder-agent'; } public static getScriptureEarthKey() { return env.SCRIPTURE_EARTH_KEY; diff --git a/src/routes/(api)/system/check/+server.ts b/src/routes/(api)/system/check/+server.ts index 33637f81..ef6a1105 100644 --- a/src/routes/(api)/system/check/+server.ts +++ b/src/routes/(api)/system/check/+server.ts @@ -1,6 +1,388 @@ +import { + BatchGetImageCommand, + DescribeImagesCommand, + DescribeRepositoriesCommand, + ECRClient, + ECRServiceException, + GetDownloadUrlForLayerCommand, + type ImageDetail, + RepositoryNotFoundException +} from '@aws-sdk/client-ecr'; import type { RequestHandler } from './$types'; +import { AWSCommon } from '$lib/server/aws/common'; // GET system/check -export const GET: RequestHandler = () => { - return new Response(JSON.stringify({ versions: {}, imageHash: '' })); +export const GET: RequestHandler = async () => { + // db connectivity handled by server hooks + // Prepare default response structure + const versions: Record = {}; + let imageHash: string | null = null; + + const repoConfig = AWSCommon.getCodeBuildImageRepo(); + const tagFilter = AWSCommon.getCodeBuildImageTag(); + const region = AWSCommon.getArtifactsBucketRegion(); + + // Status: log repo config presence + console.log(`system/check:GET - repoConfig=${repoConfig ?? '(none)'}`); + + // Try to query ECR if AWS SDK is available and repo is configured + if (repoConfig) { + console.log( + `system/check:GET - AwsEcrEcrClient available, attempting ECR query (region=${region})` + ); + try { + const client = new ECRClient({ + region + }); + + console.log('system/check:GET - EcrClient constructed'); + + // repositoryName for ECR is typically the last path segment if repo includes a path + let repoName = repoConfig; + if (repoName.includes('/')) { + const parts = repoName.split('/'); + repoName = parts.at(-1)!; + } + + console.log(`system/check:GET - resolved repositoryName=${repoName}`); + + // Verify repository exists before calling describeImages + const repoMeta = await verifyEcrRepositoryExists(client, repoName); + let imageDetails: ImageDetail[] = []; + if (!repoMeta) { + console.warn( + `system/check:GET - repository verification failed or repository not found: ${repoName} - skipping describeImages.` + ); + } else { + // Describe tagged images + console.log('system/check:GET - calling describeImages for ' + repoName); + const resp = await client.send( + new DescribeImagesCommand({ repositoryName: repoName, filter: { tagStatus: 'TAGGED' } }) + ); + imageDetails = resp['imageDetails'] ?? []; + + console.log( + `system/check:GET - describeImages returned ${imageDetails.length} imageDetails` + ); + } + + // Look for version information in image manifests + for (const img of imageDetails) { + if (!img['imageTags']) { + continue; + } + + console.log(`system/check:GET - image: ${JSON.stringify(img)}`); + + // Extract image digest (hash) from the image details - this is available without fetching manifest + const imageDigest = img['imageDigest'] || null; + console.log( + `system/check:GET - found imageDigest: ${imageDigest ?? 'none'} for image with tags: ${img['imageTags'].join(', ')}` + ); + + for (const imgTag of img['imageTags']) { + // Only process tags that match the tagFilter (if set) + if (tagFilter && !imgTag.includes(tagFilter)) { + console.log(`system/check:GET - skipping tag ${imgTag} (does not match tagFilter)`); + continue; + } + + // Extract versions for all apps from the image manifest/config + const appVersions = await fetchAllAppVersionsFromManifest(client, repoName, imgTag); + + if (Object.keys(appVersions).length) { + imageHash = imageDigest; + + for (const [app, version] of Object.entries(appVersions)) { + versions[app] = version; + console.log( + `system/check:GET - manifest-derived version for tag ${imgTag} : ${app} ${version}` + ); + } + } else { + console.log(`system/check:GET - no app versions found in manifest for tag ${imgTag}`); + } + } + } + } catch (e) { + // If ECR query fails (missing creds/permissions or network), leave versions empty + // Do not throw: the health check should still succeed if DB is OK + console.warn(`system/check:GET - ECR query failed: ${e}`); + // Detect AccessDenied and add an extra hint to logs + if (e instanceof Error && e.message.match(/AccessDenied/i)) { + console.warn( + 'system/check:GET - ECR Access Denied. Ensure IAM principal has ecr:DescribeImages for the repository and correct region/account.' + ); + } + } + } else { + console.log( + 'system/check:GET - skipping ECR query (no repoConfig or ECR client not available)' + ); + } + + // Build timestamps + const created = new Date(); + + // Debug logging + console.log(`system/check:GET - Final versions structure: ${JSON.stringify(versions)}`); + + return new Response( + JSON.stringify({ + versions, + created, + updated: created, + imageHash, + _links: { + self: { + href: `${process.env.ORIGIN || 'http://localhost:8443'}/system/check` + } + } + }) + ); }; + +const appNames = [ + 'scriptureappbuilder', + 'readingappbuilder', + 'dictionaryappbuilder', + 'keyboardappbuilder' +] as const; + +/** + * Verify that an ECR repository exists and return its metadata. + * Returns repository array on success, or null on not found / error. + * Logs info/warnings for common AWS error codes. + */ +async function verifyEcrRepositoryExists(client: ECRClient, repoName: string) { + try { + console.log( + `system/check:verifyEcrRepositoryExists - calling describeRepositories for ${repoName}` + ); + const resp = await client.send( + new DescribeRepositoriesCommand({ + repositoryNames: [repoName] + }) + ); + const repos = resp['repositories'] ?? []; + if (repos.length) { + console.log( + `system/check:verifyEcrRepositoryExists - repository found: ${repos[0]['repositoryArn'] ?? repoName}` + ); + return repos[0]; + } + console.warn( + `system/check:verifyEcrRepositoryExists - describeRepositories returned empty for ${repoName}` + ); + } catch (e) { + if (e instanceof ECRServiceException) { + console.warn( + `system/check:verifyEcrRepositoryExists - AwsException: ${e.message} awsCode=${e.name}` + ); + if (e instanceof RepositoryNotFoundException) { + console.warn(`system/check:verifyEcrRepositoryExists - repository not found: ${repoName}`); + } else if (e.message.match(/AccessDenied/i) || e.name === 'AccessDeniedException') { + console.warn( + `system/check:verifyEcrRepositoryExists - access denied for describeRepositories on ${repoName}. Ensure IAM permissions include ecr:DescribeRepositories and ecr:DescribeImages.` + ); + } else { + // Other AWS error: log and return null + console.warn(`system/check:verifyEcrRepositoryExists - unexpected ECR error: ${e.message}`); + } + } else { + console.warn(`system/check:verifyEcrRepositoryExists - unexpected error: ${e}`); + } + } + + return null; +} + +/** + * Fetch the image manifest/config and extract version information for all apps. + * Returns an associative array of app names to version strings. + * Does NOT manage caching - that's the caller's responsibility. + */ +async function fetchAllAppVersionsFromManifest( + client: ECRClient, + repoName: string, + imageTag: string +) { + try { + console.log(`system/check:fetchAllAppVersionsFromManifest - batchGetImage for ${imageTag}`); + const resp = await client.send( + new BatchGetImageCommand({ + repositoryName: repoName, + imageIds: [{ imageTag: imageTag }], + acceptedMediaTypes: [ + 'application/vnd.docker.distribution.manifest.v2+json', + 'application/vnd.oci.image.manifest.v1+json' + ] + }) + ); + + const images = resp['images']; + if (!images) { + console.log( + `system/check:fetchAllAppVersionsFromManifest - no images returned for tag ${imageTag}` + ); + return {}; + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - received ${images.length} image(s) for tag ${imageTag}` + ); + + const imageManifest = images[0]['imageManifest']; + if (!imageManifest) { + console.log( + `system/check:fetchAllAppVersionsFromManifest - imageManifest missing for ${imageTag}` + ); + return {}; + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - parsing manifest JSON for ${imageTag}` + ); + let manifest; + try { + manifest = JSON.parse(imageManifest); + } catch { + console.warn( + `system/check:fetchAllAppVersionsFromManifest - unable to decode manifest JSON for ${imageTag}\nManifest:\n${imageManifest ?? ''}` + ); + return {}; + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - manifest schema version: ${manifest['schemaVersion'] ?? 'unknown'} for ${imageTag}` + ); + + // Locate config digest in manifest (schema v2) to fetch the image config with labels + const configDigest = manifest['config']['digest']; + if (!configDigest) { + console.log( + `system/check:fetchAllAppVersionsFromManifest - no config digest found in manifest for ${imageTag}` + ); + return {}; + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - found config digest: ${configDigest} for ${imageTag}` + ); + + // Get a download URL for the config blob and fetch it + console.log( + `system/check:fetchAllAppVersionsFromManifest - getDownloadUrlForLayer for ${configDigest}` + ); + const dl = await client.send( + new GetDownloadUrlForLayerCommand({ + repositoryName: repoName, + layerDigest: configDigest + }) + ); + const url = dl['downloadUrl']; + if (!url) { + console.warn( + `system/check:fetchAllAppVersionsFromManifest - no downloadUrl for config ${configDigest}` + ); + return {}; + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - fetching config from URL for ${imageTag}` + ); + + // Fetch the config JSON + const configContent = await fetch(url).then((r) => r.text()); + if (!configContent) { + console.warn( + `system/check:fetchAllAppVersionsFromManifest - failed to fetch config from ${url}` + ); + return {}; + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - received config content (${configContent.length} bytes) for ${imageTag}` + ); + + let config; + try { + config = JSON.parse(configContent); + } catch { + console.warn( + `system/check:fetchAllAppVersionsFromManifest - unable to decode config JSON for ${imageTag}\nConfig:\n${configContent ?? ''}` + ); + return {}; + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - parsed config JSON successfully for ${imageTag}` + ); + + // Common places for labels + let labels: Record = {}; + if (config['config']['Labels']) { + labels = config['config']['Labels']; + console.log( + `system/check:fetchAllAppVersionsFromManifest - found ${labels.length} labels in config.Labels for ${imageTag}` + ); + } else if (config['container_config']['Labels']) { + labels = config['container_config']['Labels']; + console.log( + `system/check:fetchAllAppVersionsFromManifest - found ${labels.length} labels in container_config.Labels for ${imageTag}` + ); + } else { + console.log( + `system/check:fetchAllAppVersionsFromManifest - no labels found in config for ${imageTag}` + ); + } + + // Log all label keys for debugging + if (Object.keys(labels).length) { + console.log( + `system/check:fetchAllAppVersionsFromManifest - available label keys: [${Object.keys(labels).join(', ')}] for ${imageTag}` + ); + } + + // Extract version for each app from labels + const appVersions: Record = {}; + for (const app of appNames) { + const labelKey = `org.opencontainers.image.version_${app}`; + if (labels[labelKey]) { + const version = labels[labelKey]; + appVersions[app] = version; + console.log( + `system/check:fetchAllAppVersionsFromManifest - found version for ${app}: ${version} (label: ${labelKey}) for ${imageTag}` + ); + } else { + console.log( + `system/check:fetchAllAppVersionsFromManifest - no version found for ${app} (looked for label: ${labelKey}) for ${imageTag}` + ); + } + } + + console.log( + `system/check:fetchAllAppVersionsFromManifest - extracted ${Object.keys(appVersions).length} app versions for ${imageTag}: [${Object.entries( + appVersions + ) + .map(([k, v]) => `${k}=${v}`) + .join(', ')}]` + ); + + return appVersions; + } catch (e) { + if (e instanceof ECRServiceException) { + console.warn( + `system/check:fetchAllAppVersionsFromManifest - AwsException: ${e.message} awsCode=${e.name}` + ); + if (e.name === 'AccessDeniedException') { + console.warn( + 'system/check:fetchAllAppVersionsFromManifest - AccessDenied. Ensure ecr:BatchGetImage and ecr:GetDownloadUrlForLayer permissions are present.' + ); + } + } else { + console.warn(`system/check:fetchAllAppVersionsFromManifest - unexpected error: ${e}`); + } + } + return {}; +} From 71b96ba8633891a4c18fd086c36cffa948a585a9 Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Wed, 12 Nov 2025 13:05:38 -0500 Subject: [PATCH 101/144] Remove conditional around data-safety-csv (old) --- scripts/upload/default/build.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index 38bbc82c..728d8de8 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -111,13 +111,11 @@ build_apk() { fi # if building APK for Google Play, then include data safety CSV in output - echo "APPBUILDER_SCRIPT_VERSION=${APPBUILDER_SCRIPT_VERSION}" - if dpkg --compare-versions "$APPBUILDER_SCRIPT_VERSION" ge "10.3"; then - if [[ "${TARGETS}" == *"play-listing"* ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -data-safety-csv" - fi + if [[ "${TARGETS}" == *"play-listing"* ]]; then + SCRIPT_OPT="${SCRIPT_OPT} -data-safety-csv" fi + process_audio_sources process_audio_download From 2d4147463cade0b3549200ce65e5fc89ccb6d040 Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Wed, 12 Nov 2025 13:06:12 -0500 Subject: [PATCH 102/144] Conditionally build AAB+APK vs separate APK and AAB --- scripts/upload/default/build.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index 728d8de8..7a47af6d 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -146,11 +146,19 @@ build_apk() { cd "$PROJECT_DIR" || exit 1 - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} - if [[ "${BUILD_ANDROID_AAB}" == "1" ]]; then + echo "APPBUILDER_SCRIPT_VERSION=${APPBUILDER_SCRIPT_VERSION}" + # If AppBuilder >= 13.3, building AAB will also build APK, so no need for a separate APK build + if [[ "${BUILD_ANDROID_AAB}" == "1" ]] && dpkg --compare-versions "$APPBUILDER_SCRIPT_VERSION" ge "13.3"; then # shellcheck disable=SC2086 $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -app-bundle ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} + else + # If AppBuilder < 13.3, regular APK build and possible AAB build + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} + if [[ "${BUILD_ANDROID_AAB}" == "1" ]]; then + # shellcheck disable=SC2086 + $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -app-bundle ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} + fi fi # verify output -- AAPT2 is failing during appbuilder build but error is not getting back to script From 56a00ea7da6da3847c9bbe8732348f0dde458693 Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Wed, 10 Dec 2025 15:20:59 -0500 Subject: [PATCH 103/144] Allow pwa-sub-directory to be located anywhere in app-defintion In SAB 13.4, pwa-sub-directory was moved to be at the top level. --- scripts/upload/default/build.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index 7a47af6d..11b06d28 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -614,7 +614,7 @@ prepare_appbuilder_project() { INPUT_PUBLISH_PROPERTIES=$OUTPUT_PUBLISH_PROPERTIES fi if jq -e '.PUBLISH_CLOUD_REMOTE_PATH' "${INPUT_PUBLISH_PROPERTIES}" >/dev/null; then - if ! xmlstarlet sel -t -v "/app-definition/pwa-manifest/pwa-sub-directory" build.appDef 2>/dev/null; then + if ! xmlstarlet sel -t -v "count(/app-definition//pwa-sub-directory)" build.appDef 2>/dev/null; then # Note: The #/ in the variable expansion removes any leading slashes if it exists. # This makes sure the string begins with a single slash PWA_SUBDIR="/${PUBLISH_CLOUD_REMOTE_PATH#/}" @@ -622,13 +622,12 @@ prepare_appbuilder_project() { echo "PUBLISH_CLOUD_REMOTE_PATH=${PUBLISH_CLOUD_REMOTE_PATH} so update PWA Sub Directory=${PWA_SUBDIR}" APPDEF_TMP=$(mktemp) xmlstarlet ed \ - -s "/app-definition" -t elem -n "pwa-manifest" -v "" \ - -s "/app-definition/pwa-manifest" -t elem -n "pwa-sub-directory" -v "${PWA_SUBDIR}" \ + -s "/app-definition" -t elem -n "pwa-sub-directory" -v "${PWA_SUBDIR}" \ build.appDef > "${APPDEF_TMP}" cp "${APPDEF_TMP}" build.appDef fi else - PWA_SUBDIR=$(xmllint --xpath "/app-definition/pwa-manifest/pwa-sub-directory/text()" build.appDef 2>/dev/null || echo "") + PWA_SUBDIR=$(xmllint --xpath "/app-definition//pwa-sub-directory/text()" build.appDef 2>/dev/null || echo "") if [ "$PWA_SUBDIR" != "" ]; then echo "PUBLISH_CLOUD_REMOTE_PATH does not exist, but PWA Sub Directory is set." echo "PWA Sub Directory=${PWA_SUBDIR} so update PUBLISH_CLOUD_REMOTE_PATH=${PWA_SUBDIR#/}" From 88400e5f7a4b728624292ea8471cfe6d1d6a0384 Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Wed, 10 Dec 2025 16:24:05 -0500 Subject: [PATCH 104/144] Add SCRIPTURE_EARTH_DESCRIPTION --- scripts/upload/default/build.sh | 5 +++-- scripts/upload/default/publish.sh | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index 11b06d28..744453cb 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -582,7 +582,8 @@ prepare_appbuilder_project() { PUBLISH_NOTIFY_SCRIPTURE_EARTH=$(xmlstarlet sel -t -v "/app-definition/publishing/scripture-earth/@notify" build.appDef || true) PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID=$(jq -r '.["0"].relationships.idx' "${PUBLISH_SE_RECORD}") if [[ "${PUBLISH_NOTIFY_SCRIPTURE_EARTH}" == "true" ]]; then - echo "Notify Scripture Earth: id=${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID}" + SCRIPTURE_EARTH_DESCRIPTION=$(xmlstarlet sel -t -v "/app-definition/publishing/scripture-earth/@description" build.appDef || echo "") + echo "Notify Scripture Earth: id=${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID} with description='${SCRIPTURE_EARTH_DESCRIPTION}'" # If the "Notify Scripture Earth" property is enabled in the AppDef PUBLISH_TMP=$(mktemp) PUBLISH_NOTIFY_TYPE=$(jq -r '.PUBLISH_NOTIFY | type' "${PUBLISH_PROPERTIES}") @@ -601,7 +602,7 @@ prepare_appbuilder_project() { jq -cM --arg cur "${PUBLISH_NOTIFY_CURRENT}" '.PUBLISH_NOTIFY += ",SCRIPTURE_EARTH"' "${PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" fi cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" - jq -cM --arg idx "${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID}" '.SCRIPTURE_EARTH_ID = $idx' "${OUTPUT_PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" + jq -cM --arg idx "${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID}" --arg desc "${SCRIPTURE_EARTH_DESCRIPTION}" '.SCRIPTURE_EARTH_ID = $idx | .SCRIPTURE_EARTH_DESCRIPTION = $desc' "${OUTPUT_PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" fi fi diff --git a/scripts/upload/default/publish.sh b/scripts/upload/default/publish.sh index 2b787690..d160662c 100644 --- a/scripts/upload/default/publish.sh +++ b/scripts/upload/default/publish.sh @@ -369,13 +369,14 @@ notify_scripture_earth_update_json() { echo "${i_json}" | jq \ --arg type "${i_type}" \ --arg idx "${SCRIPTURE_EARTH_ID}" \ + --arg desc "${SCRIPTURE_EARTH_DESCRIPTION}" \ --arg url "${i_url}" \ --arg email "${PROJECT_OWNER_EMAIL}" \ --arg projectName "${PROJECT_NAME}" \ --arg username "${PROJECT_OWNER_NAME}" \ --arg organization "${PROJECT_ORGANIZATION}" \ --arg project "${PROJECT_URL}" \ - '. + [ { type: $type, idx: $idx, url: $url, email: $email, projectName: $projectName, username: $username, organization: $organization}]' + '. + [ { type: $type, idx: $idx, url: $url, email: $email, projectName: $projectName, description: $desc, username: $username, organization: $organization}]' } notify_scripture_earth() { From 783208ee5d78042ad0974fd11161799c0f0eb82c Mon Sep 17 00:00:00 2001 From: Chris Hubbard Date: Tue, 3 Feb 2026 10:06:03 -0500 Subject: [PATCH 105/144] SAB 13.4 PWA output directory changed --- scripts/upload/default/build.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/upload/default/build.sh b/scripts/upload/default/build.sh index 744453cb..1f1ab435 100644 --- a/scripts/upload/default/build.sh +++ b/scripts/upload/default/build.sh @@ -272,7 +272,12 @@ build_modern_pwa() { # shellcheck disable=SC2086 $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build-modern-pwa -fp pwa.output="${PWA_OUTPUT_DIR}" ${SCRIPT_OPT} - pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}/build" + # In 13.4, the output directory changed to not include /build (using rsync instead of cp -r) + if [[ -d "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}/build" ]]; then + pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}/build" + else + pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}" + fi zip -r "${OUTPUT_DIR}/pwa.zip" . popd VERSION_CODE="" From bcd20cd227e18805696b8cbb8cf0c4c8ce192423 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Tue, 3 Feb 2026 13:28:31 -0600 Subject: [PATCH 106/144] Fix DB startup error --- src/hooks.server.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/hooks.server.ts b/src/hooks.server.ts index e7ef09cd..1f80fc2b 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -41,14 +41,17 @@ if (!building) { } const heartbeat: Handle = async ({ event, resolve }) => { - if (!DatabaseConnected()) { - console.log( - 'Database connection error! Connected to Database:', - DatabaseConnected(), - 'Connected to Valkey:', - QueueConnected() - ); - throw error(503, 'Database connection error'); + // don't check db when loading login or root + if (!(event.route.id === '/(auth)/login' || event.route.id === '/(ui)')) { + if (!DatabaseConnected()) { + console.log( + 'Database connection error! Connected to Database:', + DatabaseConnected(), + 'Connected to Valkey:', + QueueConnected() + ); + throw error(503, 'Database connection error'); + } } return resolve(event); }; From 6c4e117b9c1a38d9d8a30cae9da599e0611ba63f Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 5 Feb 2026 11:26:12 -0600 Subject: [PATCH 107/144] Fix build errors --- src/lib/server/actions/project.ts | 2 +- src/lib/server/aws/codebuild.ts | 4 ++-- src/lib/server/aws/s3.ts | 2 +- src/lib/server/job-executors/build.ts | 2 +- src/lib/server/job-executors/polling.ts | 4 ++-- src/lib/server/job-executors/release.ts | 4 ++-- src/lib/server/job-executors/s3.ts | 6 +++--- src/lib/{ => server}/models/artifacts.ts | 0 src/lib/{ => server}/models/build.ts | 0 src/lib/{ => server}/models/job.ts | 0 src/lib/{ => server}/models/project.ts | 0 src/lib/{ => server}/models/release.ts | 0 .../job/[jobId=idNumber]/build/+server.ts | 2 +- .../build/[buildId=idNumber]/+server.ts | 4 ++-- .../release/[releaseId=idNumber]/+server.ts | 2 +- src/routes/(auth)/login/+page.svelte | 20 ++++++------------- src/routes/(ui)/+layout.svelte | 8 ++++---- .../(ui)/build-admin/view/+page.server.ts | 2 +- .../(ui)/release-admin/view/+page.server.ts | 4 +++- .../(ui)/release-admin/view/+page.svelte | 3 +-- src/routes/+layout.svelte | 2 +- 21 files changed, 32 insertions(+), 39 deletions(-) rename src/lib/{ => server}/models/artifacts.ts (100%) rename src/lib/{ => server}/models/build.ts (100%) rename src/lib/{ => server}/models/job.ts (100%) rename src/lib/{ => server}/models/project.ts (100%) rename src/lib/{ => server}/models/release.ts (100%) diff --git a/src/lib/server/actions/project.ts b/src/lib/server/actions/project.ts index 7bf686b4..52207303 100644 --- a/src/lib/server/actions/project.ts +++ b/src/lib/server/actions/project.ts @@ -2,7 +2,7 @@ import type { Prisma } from '@prisma/client'; import { IAmWrapper } from '../aws/iamwrapper'; import { prisma } from '../prisma'; import { Utils } from '../utils'; -import { Project } from '$lib/models/project'; +import { Project } from '$lib/server/models/project'; export class ProjectUpdateOperation { private id; diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index f47d869a..7327889e 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -16,8 +16,8 @@ import { getArtifactFilename, getArtifactPath, getBasePrefixUrl -} from '$lib/models/artifacts'; -import { Job } from '$lib/models/job'; +} from '$lib/server/models/artifacts'; +import { Job } from '$lib/server/models/job'; import { Utils } from '$lib/server/utils'; export type ReleaseForCodeBuild = Prisma.releaseGetPayload<{ diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index 85a32875..8343c168 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -20,7 +20,7 @@ import { beginArtifacts, getBasePrefixUrl, handleArtifact -} from '$lib/models/artifacts'; +} from '$lib/server/models/artifacts'; import { Utils } from '$lib/server/utils'; export class S3 extends AWSCommon { diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index 4891368e..3909fc78 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -2,10 +2,10 @@ import type { Prisma } from '@prisma/client'; import type { Job } from 'bullmq'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { Build } from '../../models/build'; import { CodeBuild } from '../aws/codebuild'; import { CodeCommit } from '../aws/codecommit'; import { BullMQ, getQueues } from '../bullmq'; +import { Build } from '../models/build'; import { prisma } from '../prisma'; export async function product(job: Job): Promise { diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index e96e7b84..30e8278d 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -1,10 +1,10 @@ import type { Prisma } from '@prisma/client'; import type { Job } from 'bullmq'; -import { Build } from '../../models/build'; import { CodeBuild } from '../aws/codebuild'; import { BullMQ, getQueues } from '../bullmq'; +import { Build } from '../models/build'; import { prisma } from '../prisma'; -import { Release } from '$lib/models/release'; +import { Release } from '$lib/server/models/release'; export async function build(job: Job): Promise { try { diff --git a/src/lib/server/job-executors/release.ts b/src/lib/server/job-executors/release.ts index b902e9a4..4a290279 100644 --- a/src/lib/server/job-executors/release.ts +++ b/src/lib/server/job-executors/release.ts @@ -3,8 +3,8 @@ import { readFile } from 'fs/promises'; import { CodeBuild } from '../aws/codebuild'; import type { BullMQ } from '../bullmq'; import { prisma } from '../prisma'; -import { Build } from '$lib/models/build'; -import { Release } from '$lib/models/release'; +import { Build } from '$lib/server/models/build'; +import { Release } from '$lib/server/models/release'; export async function product(job: Job): Promise { try { diff --git a/src/lib/server/job-executors/s3.ts b/src/lib/server/job-executors/s3.ts index b2e1e5c8..e165a8cc 100644 --- a/src/lib/server/job-executors/s3.ts +++ b/src/lib/server/job-executors/s3.ts @@ -1,10 +1,10 @@ import type { Job } from 'bullmq'; import { readFile } from 'node:fs/promises'; -import type { BuildForPrefix } from '$lib/models/artifacts'; -import { Build } from '$lib/models/build'; -import { Release } from '$lib/models/release'; import { S3 } from '$lib/server/aws/s3'; import type { BullMQ } from '$lib/server/bullmq'; +import type { BuildForPrefix } from '$lib/server/models/artifacts'; +import { Build } from '$lib/server/models/build'; +import { Release } from '$lib/server/models/release'; import { prisma } from '$lib/server/prisma'; export async function save(job: Job): Promise { diff --git a/src/lib/models/artifacts.ts b/src/lib/server/models/artifacts.ts similarity index 100% rename from src/lib/models/artifacts.ts rename to src/lib/server/models/artifacts.ts diff --git a/src/lib/models/build.ts b/src/lib/server/models/build.ts similarity index 100% rename from src/lib/models/build.ts rename to src/lib/server/models/build.ts diff --git a/src/lib/models/job.ts b/src/lib/server/models/job.ts similarity index 100% rename from src/lib/models/job.ts rename to src/lib/server/models/job.ts diff --git a/src/lib/models/project.ts b/src/lib/server/models/project.ts similarity index 100% rename from src/lib/models/project.ts rename to src/lib/server/models/project.ts diff --git a/src/lib/models/release.ts b/src/lib/server/models/release.ts similarity index 100% rename from src/lib/models/release.ts rename to src/lib/server/models/release.ts diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts index 1ef75a18..d8c0c536 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; -import { Build } from '$lib/models/build'; import { BullMQ, getQueues } from '$lib/server/bullmq'; +import { Build } from '$lib/server/models/build'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts index 0103c498..002f37d9 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts @@ -1,8 +1,8 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; -import { Build } from '$lib/models/build'; -import { Release } from '$lib/models/release'; import { BullMQ, getQueues } from '$lib/server/bullmq'; +import { Build } from '$lib/server/models/build'; +import { Release } from '$lib/server/models/release'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts index f00bf3bd..f4a107ff 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts @@ -1,5 +1,5 @@ import type { RequestHandler } from './$types'; -import { Release } from '$lib/models/release'; +import { Release } from '$lib/server/models/release'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 4480c664..80c33be6 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -1,17 +1,15 @@
    @@ -38,5 +45,26 @@ SIL Global ) : Programming

    +

    AppBuilder Versions

    +

    + Hash: + {data.appVersions[0].imageHash} +

    +

    + Updated: + {data.appVersions[0].updated?.toLocaleString()} +

    +
    + + + {#each data.appVersions.toSorted((a, b) => a.appName.localeCompare(b.appName)) as version} + + + + + {/each} + +
    {version.appName}:{version.version}
    +
    From 5f229a08efac91230258e4e8aa2b88786e19f62c Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 25 Feb 2026 11:38:25 -0600 Subject: [PATCH 120/144] Enforce string length (#76) * Add utility function to enforce length * Enforce length in CRUD * Enforce length in jobs * Change log function for trim strings * Add maxlength attribute to text fields Fix loop condition --- src/lib/server/actions/project.ts | 14 ++-- src/lib/server/job-executors/build.ts | 47 +++++++---- src/lib/server/job-executors/polling.ts | 27 ++++--- src/lib/server/job-executors/release.ts | 31 ++++--- src/lib/server/job-executors/s3.ts | 19 +++-- src/lib/utils/index.ts | 3 + src/lib/valibot.ts | 81 ++++++++++++++++++- src/routes/(api)/job/+server.ts | 8 +- .../(api)/job/[jobId=idNumber]/+server.ts | 3 +- .../job/[jobId=idNumber]/build/+server.ts | 3 +- .../build/[buildId=idNumber]/+server.ts | 3 +- src/routes/(api)/project/+server.ts | 5 +- .../(ui)/build-admin/update/+page.server.ts | 23 ++++-- .../(ui)/build-admin/update/+page.svelte | 45 +++++++++-- .../(ui)/client-admin/create/+page.svelte | 10 ++- .../(ui)/client-admin/update/+page.svelte | 10 ++- src/routes/(ui)/client-admin/valibot.ts | 7 +- .../(ui)/job-admin/update/+page.server.ts | 19 +++-- src/routes/(ui)/job-admin/update/+page.svelte | 27 ++++++- .../(ui)/project-admin/update/+page.server.ts | 31 ++++--- .../(ui)/project-admin/update/+page.svelte | 71 +++++++++++++--- .../(ui)/release-admin/update/+page.server.ts | 29 ++++--- .../(ui)/release-admin/update/+page.svelte | 67 ++++++++++++--- 23 files changed, 454 insertions(+), 129 deletions(-) diff --git a/src/lib/server/actions/project.ts b/src/lib/server/actions/project.ts index 52207303..b2d1c69a 100644 --- a/src/lib/server/actions/project.ts +++ b/src/lib/server/actions/project.ts @@ -3,6 +3,7 @@ import { IAmWrapper } from '../aws/iamwrapper'; import { prisma } from '../prisma'; import { Utils } from '../utils'; import { Project } from '$lib/server/models/project'; +import { trimStrings } from '$lib/valibot'; export class ProjectUpdateOperation { private id; @@ -66,11 +67,14 @@ export class ProjectUpdateOperation { const url = this.adjustUrl(project.url!, publicKeyId!); await prisma.project.update({ where: { id: project.id }, - data: { - user_id, - publishing_key, - url - } + data: trimStrings( + { + user_id, + publishing_key, + url + }, + 'project' + ) }); } private adjustUrl(url: string, newPublicKeyId: string) { diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index 2842e74d..193492f5 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -7,6 +7,7 @@ import { CodeCommit } from '../aws/codecommit'; import { BullMQ, getQueues } from '../bullmq'; import { Build } from '../models/build'; import { prisma } from '../prisma'; +import { trimStrings } from '$lib/valibot'; export async function product(job: Job): Promise { try { @@ -65,12 +66,16 @@ export async function product(job: Job): Promise if (lastBuildGuid) { await prisma.build.update({ where: { id: build.id }, - data: { - build_guid: lastBuildGuid, - codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), - console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), - status: Build.Status.Active - } + data: trimStrings( + { + build_guid: lastBuildGuid, + codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), + console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), + status: Build.Status.Active + }, + 'build', + job.log + ) }); } const name = `Check status of Build #${build.id}`; @@ -110,12 +115,16 @@ export async function product(job: Job): Promise if (lastBuildGuid) { await prisma.build.update({ where: { id: build.id }, - data: { - build_guid: lastBuildGuid, - codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), - console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), - status: Build.Status.Active - } + data: trimStrings( + { + build_guid: lastBuildGuid, + codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), + console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), + status: Build.Status.Active + }, + 'build', + job.log + ) }); } const name = `Check status of Build #${build.id}`; @@ -136,11 +145,15 @@ export async function product(job: Job): Promise job.log(`${e}`); await prisma.build.update({ where: { id: job.data.buildId }, - data: { - result: Build.Result.Failure, - status: Build.Status.Completed, - error: String(e) - } + data: trimStrings( + { + result: Build.Result.Failure, + status: Build.Status.Completed, + error: String(e) + }, + 'build', + job.log + ) }); } } diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index 03951356..3a0e2421 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -5,6 +5,8 @@ import { BullMQ, getQueues } from '../bullmq'; import { Build } from '../models/build'; import { prisma } from '../prisma'; import { Release } from '$lib/server/models/release'; +import type { Logger } from '$lib/utils'; +import { trimStrings } from '$lib/valibot'; export async function build(job: Job): Promise { try { @@ -56,7 +58,7 @@ export async function build(job: Job): Promise { } await prisma.build.update({ where: { id: build.id }, - data: { ...build, job: undefined } + data: trimStrings({ ...build, job: undefined }, 'build', job.log) }); job.updateProgress(100); return { @@ -71,11 +73,15 @@ export async function build(job: Job): Promise { job.log(`${e}`); await prisma.build.update({ where: { id: job.data.buildId }, - data: { - result: Build.Result.Failure, - status: Build.Status.Completed, - error: String(e) - } + data: trimStrings( + { + result: Build.Result.Failure, + status: Build.Status.Completed, + error: String(e) + }, + 'build', + job.log + ) }); } } @@ -123,11 +129,11 @@ export async function release(job: Job): Promise): Promise + release: Prisma.releaseGetPayload<{ select: { id: true; console_text_url: true } }>, + log: Logger ) { await prisma.release.update({ where: { id: release.id }, - data: { error: release.console_text_url } + data: trimStrings({ error: release.console_text_url }, 'release', log) }); await getQueues().S3.add(`Save Errors for Release ${release.id} to S3`, { type: BullMQ.JobType.S3_CopyError, diff --git a/src/lib/server/job-executors/release.ts b/src/lib/server/job-executors/release.ts index f69ff49d..deefcee9 100644 --- a/src/lib/server/job-executors/release.ts +++ b/src/lib/server/job-executors/release.ts @@ -5,6 +5,7 @@ import { BullMQ, getQueues } from '../bullmq'; import { prisma } from '../prisma'; import { Build } from '$lib/server/models/build'; import { Release } from '$lib/server/models/release'; +import { trimStrings } from '$lib/valibot'; export async function product(job: Job): Promise { try { @@ -32,12 +33,16 @@ export async function product(job: Job): Promise): Promise): Promise { const id = job.data.id; @@ -31,7 +32,7 @@ export async function save(job: Job): Promise if (build?.job) { await s3.copyS3Folder(build); let defaultLanguage = await s3.readS3File(build, 'play-listing/default-language.txt'); - console.log(`getExtraContent defaultLanguage: ${defaultLanguage}`); + job.log(`getExtraContent defaultLanguage: ${defaultLanguage}`); const manifestFileContent = await s3.readS3File(build, 'manifest.txt'); let manifest: Record> = {}; if (manifestFileContent) { @@ -104,12 +105,16 @@ export async function save(job: Job): Promise } await prisma.build.update({ where: { id }, - data: { - ...build, - status: Build.Status.Completed, - result: Build.Result.Success, - job: undefined - } + data: trimStrings( + { + ...build, + status: Build.Status.Completed, + result: Build.Result.Success, + job: undefined + }, + 'build', + job.log + ) }); await s3.removeCodeBuildFolder(build); job.updateProgress(100); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 5a187072..a50e4faa 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1,3 +1,6 @@ export function ErrorResponse(status: number, message: string, headers?: HeadersInit) { return new Response(JSON.stringify({ status, message }), { status, headers }); } + +export type Logger = (msg: string) => void; +export const defaultLogger: Logger = (msg) => console.log(msg); diff --git a/src/lib/valibot.ts b/src/lib/valibot.ts index e4fb9598..cbd25fd4 100644 --- a/src/lib/valibot.ts +++ b/src/lib/valibot.ts @@ -1,4 +1,5 @@ import * as v from 'valibot'; +import { type Logger, defaultLogger } from './utils'; export const idSchema = v.pipe(v.number(), v.minValue(0), v.integer()); @@ -7,14 +8,14 @@ export const paramNumber = v.pipe( v.transform((s) => parseInt(s)) ); -export function convertEmptyStrToNull() { +export function convertEmptyStrToNull(limit?: number) { return v.nullable( v.union([ v.pipe( v.literal(''), v.transform(() => null) ), - v.string() + limit ? v.pipe(v.string(), v.maxBytes(limit)) : v.string() ]) ); } @@ -42,3 +43,79 @@ export const tableSchema = v.object({ }) ) }); + +export const stringLimits = { + build: { + status: 255, + result: 255, + error: 2083, + channel: 255, + artifact_url_base: 2083, + artifact_files: 4096, + build_guid: 255, + console_text_url: 255, + codebuild_url: 255, + targets: 255 + }, + client: { + access_token: 255, + prefix: 4 + }, + job: { + request_id: 255, + git_url: 2083, + app_id: 255, + publisher_id: 255, + jenkins_build_url: 1024, + jenkins_publish_url: 1024 + }, + project: { + status: 255, + result: 255, + error: 2083, + url: 1024, + user_id: 255, + group_id: 255, + app_id: 255, + project_name: 255, + language_code: 255, + publishing_key: 1024 + }, + release: { + status: 255, + result: 255, + error: 2083, + channel: 255, + title: 30, + defaultLanguage: 255, + promote_from: 255, + build_guid: 255, + console_text_url: 255, + codebuild_url: 255, + targets: 255, + artifact_url_base: 255, + artifact_files: 255 + } +} as const; + +export function trimStrings>( + obj: T, + scope: keyof typeof stringLimits, + log: Logger = defaultLogger +) { + for (const [key, limit] of Object.entries(stringLimits[scope])) { + const raw = obj[key]; + if (raw) { + let val = (raw as string).trim().substring(0, limit); + while (new Blob([val]).size > limit) { + val = val.substring(0, val.length - 1); + } + if (raw !== val) { + log(`trimStrings ${scope}: "${raw}" => "${val}"`); + //@ts-expect-error this should be fine... + obj[key] = val; + } + } + } + return obj; +} diff --git a/src/routes/(api)/job/+server.ts b/src/routes/(api)/job/+server.ts index d649cb97..8f12185c 100644 --- a/src/routes/(api)/job/+server.ts +++ b/src/routes/(api)/job/+server.ts @@ -2,13 +2,13 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; -import { stringIdSchema } from '$lib/valibot'; +import { stringIdSchema, stringLimits } from '$lib/valibot'; const jobSchema = v.strictObject({ request_id: stringIdSchema, - git_url: v.pipe(v.string(), v.url()), - app_id: v.string(), - publisher_id: v.string() + git_url: v.pipe(v.string(), v.url(), v.maxBytes(stringLimits.job.git_url)), + app_id: v.pipe(v.string(), v.maxBytes(stringLimits.job.app_id)), + publisher_id: v.pipe(v.string(), v.maxBytes(stringLimits.job.publisher_id)) }); // POST /job diff --git a/src/routes/(api)/job/[jobId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/+server.ts index 67426c54..55bdccaf 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/+server.ts @@ -2,6 +2,7 @@ import * as v from 'valibot'; import type { RequestHandler } from './$types'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; +import { stringLimits } from '$lib/valibot'; // GET /job/[id] export const GET: RequestHandler = async ({ params }) => { @@ -33,7 +34,7 @@ export const GET: RequestHandler = async ({ params }) => { }; const updateSchema = v.strictObject({ - publisher_id: v.string() + publisher_id: v.pipe(v.string(), v.maxBytes(stringLimits.job.publisher_id)) }); // PUT /job/[id] diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts index d8c0c536..8ae41e0e 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/+server.ts @@ -4,9 +4,10 @@ import { BullMQ, getQueues } from '$lib/server/bullmq'; import { Build } from '$lib/server/models/build'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; +import { stringLimits } from '$lib/valibot'; const buildSchema = v.strictObject({ - targets: v.string(), + targets: v.pipe(v.string(), v.maxBytes(stringLimits.build.targets)), environment: v.record(v.string(), v.string()) }); diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts index 002f37d9..8d6b8ae4 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts @@ -5,6 +5,7 @@ import { Build } from '$lib/server/models/build'; import { Release } from '$lib/server/models/release'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; +import { stringLimits } from '$lib/valibot'; // GET /job/[id]/build/[id] export const GET: RequestHandler = async ({ params }) => { @@ -55,7 +56,7 @@ export const GET: RequestHandler = async ({ params }) => { }; const releaseSchema = v.strictObject({ - targets: v.string(), + targets: v.pipe(v.string(), v.maxBytes(stringLimits.release.targets)), channel: v.pipe(v.string(), v.picklist(['alpha', 'beta', 'production'])), environment: v.record(v.string(), v.string()) }); diff --git a/src/routes/(api)/project/+server.ts b/src/routes/(api)/project/+server.ts index 3bf80fe6..d626bbbf 100644 --- a/src/routes/(api)/project/+server.ts +++ b/src/routes/(api)/project/+server.ts @@ -4,6 +4,7 @@ import type { RequestHandler } from './$types'; import { AWSCommon } from '$lib/server/aws/common'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; +import { stringLimits } from '$lib/valibot'; const projectSchema = v.strictObject({ app_id: v.pipe( @@ -15,8 +16,8 @@ const projectSchema = v.strictObject({ 'keyboardappbuilder' ]) ), - project_name: v.string(), - language_code: v.string(), + project_name: v.pipe(v.string(), v.maxBytes(stringLimits.project.project_name)), + language_code: v.pipe(v.string(), v.maxBytes(stringLimits.project.language_code)), storage_type: v.literal('s3') }); diff --git a/src/routes/(ui)/build-admin/update/+page.server.ts b/src/routes/(ui)/build-admin/update/+page.server.ts index 95825885..acb794f0 100644 --- a/src/routes/(ui)/build-admin/update/+page.server.ts +++ b/src/routes/(ui)/build-admin/update/+page.server.ts @@ -9,20 +9,27 @@ import { idSchema, paramNumber, selectFrom, - stringIdSchema + stringIdSchema, + stringLimits } from '$lib/valibot'; const buildSchema = v.object({ job_id: idSchema, - status: convertEmptyStrToNull(), + status: v.pipe(convertEmptyStrToNull(stringLimits.build.status)), build_guid: v.pipe(convertEmptyStrToNull(), v.nullable(stringIdSchema)), - result: convertEmptyStrToNull(), - error: v.pipe(convertEmptyStrToNull(), v.nullable(v.pipe(v.string(), v.url()))), - artifact_url_base: v.pipe(convertEmptyStrToNull(), v.nullable(v.pipe(v.string(), v.url()))), - artifact_files: convertEmptyStrToNull(), - channel: convertEmptyStrToNull(), + result: convertEmptyStrToNull(stringLimits.build.result), + error: v.pipe( + convertEmptyStrToNull(stringLimits.build.error), + v.nullable(v.pipe(v.string(), v.url())) + ), + artifact_url_base: v.pipe( + convertEmptyStrToNull(stringLimits.build.artifact_url_base), + v.nullable(v.pipe(v.string(), v.url())) + ), + artifact_files: convertEmptyStrToNull(stringLimits.build.artifact_files), + channel: convertEmptyStrToNull(stringLimits.build.channel), version_code: v.nullable(idSchema), - targets: convertEmptyStrToNull(), + targets: convertEmptyStrToNull(stringLimits.build.targets), environment: convertEmptyStrToNull() }); diff --git a/src/routes/(ui)/build-admin/update/+page.svelte b/src/routes/(ui)/build-admin/update/+page.svelte index 3d72eb6a..312f6981 100644 --- a/src/routes/(ui)/build-admin/update/+page.svelte +++ b/src/routes/(ui)/build-admin/update/+page.svelte @@ -5,6 +5,7 @@ import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import LabeledFormInput from '$lib/components/LabeledFormInput.svelte'; import { title } from '$lib/stores'; + import { stringLimits } from '$lib/valibot'; const id = $derived(page.url.searchParams.get('id')!); @@ -42,19 +43,39 @@   - +   - +   - +   - +   @@ -63,6 +84,7 @@ type="url" bind:value={$form.artifact_url_base} required + maxlength={stringLimits.build.artifact_url_base} />   @@ -70,11 +92,17 @@   - +   @@ -82,7 +110,12 @@   - +   diff --git a/src/routes/(ui)/client-admin/create/+page.svelte b/src/routes/(ui)/client-admin/create/+page.svelte index 8980f7f7..b9ed24eb 100644 --- a/src/routes/(ui)/client-admin/create/+page.svelte +++ b/src/routes/(ui)/client-admin/create/+page.svelte @@ -4,6 +4,7 @@ import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import LabeledFormInput from '$lib/components/LabeledFormInput.svelte'; import { title } from '$lib/stores'; + import { stringLimits } from '$lib/valibot'; $title = 'Create Client'; @@ -32,11 +33,18 @@ type="text" bind:value={$form.access_token} required + maxlength={stringLimits.client.access_token} />   - +   diff --git a/src/routes/(ui)/client-admin/update/+page.svelte b/src/routes/(ui)/client-admin/update/+page.svelte index 144b96eb..d113ddb4 100644 --- a/src/routes/(ui)/client-admin/update/+page.svelte +++ b/src/routes/(ui)/client-admin/update/+page.svelte @@ -5,6 +5,7 @@ import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import LabeledFormInput from '$lib/components/LabeledFormInput.svelte'; import { title } from '$lib/stores'; + import { stringLimits } from '$lib/valibot'; const id = $derived(page.url.searchParams.get('id')!); @@ -38,11 +39,18 @@ type="text" bind:value={$form.access_token} required + maxlength={stringLimits.client.access_token} />   - +   diff --git a/src/routes/(ui)/client-admin/valibot.ts b/src/routes/(ui)/client-admin/valibot.ts index d69af0fb..ccb06d5d 100644 --- a/src/routes/(ui)/client-admin/valibot.ts +++ b/src/routes/(ui)/client-admin/valibot.ts @@ -1,14 +1,17 @@ import * as v from 'valibot'; +import { stringLimits } from '$lib/valibot'; export const clientSchema = v.strictObject({ access_token: v.pipe( v.string(), v.transform((s) => s.trim()), - v.minLength(1) + v.minLength(1), + v.maxBytes(stringLimits.client.access_token) ), prefix: v.pipe( v.string(), v.transform((s) => s.trim()), - v.minLength(1) + v.minLength(1), + v.maxBytes(stringLimits.client.prefix) ) }); diff --git a/src/routes/(ui)/job-admin/update/+page.server.ts b/src/routes/(ui)/job-admin/update/+page.server.ts index 49639fa8..43584cdd 100644 --- a/src/routes/(ui)/job-admin/update/+page.server.ts +++ b/src/routes/(ui)/job-admin/update/+page.server.ts @@ -9,18 +9,25 @@ import { idSchema, paramNumber, selectFrom, - stringIdSchema + stringIdSchema, + stringLimits } from '$lib/valibot'; const jobSchema = v.strictObject({ request_id: stringIdSchema, - git_url: v.pipe(v.string(), v.url()), - app_id: v.string(), - publisher_id: v.string(), + git_url: v.pipe(v.string(), v.url(), v.maxBytes(stringLimits.job.git_url)), + app_id: v.pipe(v.string(), v.maxBytes(stringLimits.job.app_id)), + publisher_id: v.pipe(v.string(), v.maxBytes(stringLimits.job.publisher_id)), client_id: v.nullable(idSchema), existing_version_code: v.nullable(idSchema), - jenkins_build_url: v.pipe(convertEmptyStrToNull(), v.nullable(v.pipe(v.string(), v.url()))), - jenkins_publish_url: v.pipe(convertEmptyStrToNull(), v.nullable(v.pipe(v.string(), v.url()))) + jenkins_build_url: v.pipe( + convertEmptyStrToNull(stringLimits.job.jenkins_build_url), + v.nullable(v.pipe(v.string(), v.url())) + ), + jenkins_publish_url: v.pipe( + convertEmptyStrToNull(stringLimits.job.jenkins_publish_url), + v.nullable(v.pipe(v.string(), v.url())) + ) }); export const load = (async ({ url }) => { diff --git a/src/routes/(ui)/job-admin/update/+page.svelte b/src/routes/(ui)/job-admin/update/+page.svelte index 2269635b..227ee026 100644 --- a/src/routes/(ui)/job-admin/update/+page.svelte +++ b/src/routes/(ui)/job-admin/update/+page.svelte @@ -5,6 +5,7 @@ import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import LabeledFormInput from '$lib/components/LabeledFormInput.svelte'; import { title } from '$lib/stores'; + import { stringLimits } from '$lib/valibot'; const id = $derived(page.url.searchParams.get('id')!); @@ -38,15 +39,28 @@ type="text" bind:value={$form.request_id} required + maxlength={stringLimits.job.request_id} />   - +   - +   @@ -55,6 +69,7 @@ type="text" bind:value={$form.publisher_id} required + maxlength={stringLimits.job.publisher_id} />   @@ -76,7 +91,12 @@   - +   @@ -84,6 +104,7 @@ class="input input-bordered validator" type="url" bind:value={$form.jenkins_publish_url} + maxlength={stringLimits.job.jenkins_publish_url} />   diff --git a/src/routes/(ui)/project-admin/update/+page.server.ts b/src/routes/(ui)/project-admin/update/+page.server.ts index 3c24f7ca..2eb5ea54 100644 --- a/src/routes/(ui)/project-admin/update/+page.server.ts +++ b/src/routes/(ui)/project-admin/update/+page.server.ts @@ -4,20 +4,29 @@ import { valibot } from 'sveltekit-superforms/adapters'; import * as v from 'valibot'; import type { Actions, PageServerLoad } from './$types'; import { prisma } from '$lib/server/prisma'; -import { convertEmptyStrToNull, idSchema, paramNumber, selectFrom } from '$lib/valibot'; +import { + convertEmptyStrToNull, + idSchema, + paramNumber, + selectFrom, + stringLimits +} from '$lib/valibot'; const projectSchema = v.object({ - status: convertEmptyStrToNull(), - result: convertEmptyStrToNull(), - error: convertEmptyStrToNull(), - url: v.pipe(convertEmptyStrToNull(), v.nullable(v.pipe(v.string(), v.url()))), - user_id: convertEmptyStrToNull(), - group_id: convertEmptyStrToNull(), - app_id: convertEmptyStrToNull(), + status: convertEmptyStrToNull(stringLimits.project.status), + result: convertEmptyStrToNull(stringLimits.project.result), + error: convertEmptyStrToNull(stringLimits.project.error), + url: v.pipe( + convertEmptyStrToNull(stringLimits.project.url), + v.nullable(v.pipe(v.string(), v.url())) + ), + user_id: convertEmptyStrToNull(stringLimits.project.user_id), + group_id: convertEmptyStrToNull(stringLimits.project.group_id), + app_id: convertEmptyStrToNull(stringLimits.project.app_id), client_id: v.nullable(idSchema), - project_name: convertEmptyStrToNull(), - language_code: convertEmptyStrToNull(), - publishing_key: convertEmptyStrToNull() + project_name: convertEmptyStrToNull(stringLimits.project.project_name), + language_code: convertEmptyStrToNull(stringLimits.project.language_code), + publishing_key: convertEmptyStrToNull(stringLimits.project.publishing_key) }); export const load = (async ({ url }) => { diff --git a/src/routes/(ui)/project-admin/update/+page.svelte b/src/routes/(ui)/project-admin/update/+page.svelte index 2df17818..9300bf7d 100644 --- a/src/routes/(ui)/project-admin/update/+page.svelte +++ b/src/routes/(ui)/project-admin/update/+page.svelte @@ -5,6 +5,7 @@ import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import LabeledFormInput from '$lib/components/LabeledFormInput.svelte'; import { title } from '$lib/stores'; + import { stringLimits } from '$lib/valibot'; const id = $derived(page.url.searchParams.get('id')!); @@ -33,31 +34,66 @@
    - +   - +   - +   - +   - +   - +   - +   @@ -65,15 +101,30 @@   - +   - +   - +   diff --git a/src/routes/(ui)/release-admin/update/+page.server.ts b/src/routes/(ui)/release-admin/update/+page.server.ts index 8571a11e..4b81cd81 100644 --- a/src/routes/(ui)/release-admin/update/+page.server.ts +++ b/src/routes/(ui)/release-admin/update/+page.server.ts @@ -9,23 +9,30 @@ import { idSchema, paramNumber, selectFrom, - stringIdSchema + stringIdSchema, + stringLimits } from '$lib/valibot'; const releaseSchema = v.object({ build_id: idSchema, - status: convertEmptyStrToNull(), - result: convertEmptyStrToNull(), - error: v.pipe(convertEmptyStrToNull(), v.nullable(v.pipe(v.string(), v.url()))), - channel: v.string(), - title: convertEmptyStrToNull(), - defaultLanguage: convertEmptyStrToNull(), + status: convertEmptyStrToNull(stringLimits.release.status), + result: convertEmptyStrToNull(stringLimits.release.result), + error: v.pipe( + convertEmptyStrToNull(stringLimits.release.error), + v.nullable(v.pipe(v.string(), v.url())) + ), + channel: v.pipe(v.string(), v.maxBytes(stringLimits.release.channel)), + title: convertEmptyStrToNull(stringLimits.release.title), + defaultLanguage: convertEmptyStrToNull(stringLimits.release.defaultLanguage), build_guid: v.pipe(convertEmptyStrToNull(), v.nullable(stringIdSchema)), - promote_from: convertEmptyStrToNull(), - targets: convertEmptyStrToNull(), + promote_from: convertEmptyStrToNull(stringLimits.release.promote_from), + targets: convertEmptyStrToNull(stringLimits.release.targets), environment: convertEmptyStrToNull(), - artifact_url_base: v.pipe(convertEmptyStrToNull(), v.nullable(v.pipe(v.string(), v.url()))), - artifact_files: convertEmptyStrToNull() + artifact_url_base: v.pipe( + convertEmptyStrToNull(stringLimits.release.artifact_url_base), + v.nullable(v.pipe(v.string(), v.url())) + ), + artifact_files: convertEmptyStrToNull(stringLimits.release.artifact_files) }); export const load = (async ({ url }) => { diff --git a/src/routes/(ui)/release-admin/update/+page.svelte b/src/routes/(ui)/release-admin/update/+page.svelte index a4b3a837..02a014b5 100644 --- a/src/routes/(ui)/release-admin/update/+page.svelte +++ b/src/routes/(ui)/release-admin/update/+page.svelte @@ -5,6 +5,7 @@ import Breadcrumbs from '$lib/components/Breadcrumbs.svelte'; import LabeledFormInput from '$lib/components/LabeledFormInput.svelte'; import { title } from '$lib/stores'; + import { stringLimits } from '$lib/valibot'; const id = $derived(page.url.searchParams.get('id')!); @@ -42,39 +43,85 @@   - +   - +   - +   - +   - +   - +   - +   - +   - +   @@ -87,6 +134,7 @@ type="url" bind:value={$form.artifact_url_base} required + maxlength={stringLimits.release.artifact_url_base} />   @@ -94,6 +142,7 @@   From 8afc8d8de78e3143eb4eb606e90ab4fadcbe0614 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 25 Feb 2026 11:48:35 -0600 Subject: [PATCH 121/144] Add OTEL instrumentation (#74) * Configure docker images * Install packages * Basic instrumentation * Trace authentication * Change dataset name in config * Add OTEL to BullMQ * Add job lifetime options * Add telemetry for CodeCommit * Add telemetry for IAM * Add telemetry for CodeBuild * Add telemetry for S3 * Replace other unneeded console logs --- Dockerfile.otel | 4 + deployment/development/docker-compose.yml | 16 + package-lock.json | 988 +++++++++++++++++- package.json | 11 + src/hooks.server.ts | 106 +- src/lib/otel/development_config.yml | 21 + src/lib/otel/index.ts | 114 ++ src/lib/otel/production_config.yml | 123 +++ src/lib/otel/receivers_config.yml | 7 + src/lib/server/auth.ts | 10 + src/lib/server/aws/codebuild.ts | 498 +++++---- src/lib/server/aws/codecommit.ts | 119 ++- src/lib/server/aws/iamwrapper.ts | 4 +- src/lib/server/aws/s3.ts | 77 +- src/lib/server/bullmq/BullWorker.ts | 34 +- src/lib/server/bullmq/queues.ts | 32 +- src/lib/server/prisma.ts | 5 + src/routes/(auth)/exchange/+server.ts | 5 + .../(ui)/build-admin/update/+page.server.ts | 9 +- .../(ui)/job-admin/update/+page.server.ts | 9 +- .../(ui)/project-admin/update/+page.server.ts | 9 +- .../(ui)/release-admin/update/+page.server.ts | 9 +- 22 files changed, 1887 insertions(+), 323 deletions(-) create mode 100644 Dockerfile.otel create mode 100644 src/lib/otel/development_config.yml create mode 100644 src/lib/otel/index.ts create mode 100644 src/lib/otel/production_config.yml create mode 100644 src/lib/otel/receivers_config.yml diff --git a/Dockerfile.otel b/Dockerfile.otel new file mode 100644 index 00000000..a7abdfc9 --- /dev/null +++ b/Dockerfile.otel @@ -0,0 +1,4 @@ +FROM otel/opentelemetry-collector-contrib:0.136.0 +COPY src/lib/otel/receivers_config.yml /etc/otel/receivers_config.yml +COPY src/lib/otel/production_config.yml /etc/otel/production_config.yml +CMD ["--config", "/etc/otel/production_config.yml", "--config", "/etc/otel/receivers_config.yml"] \ No newline at end of file diff --git a/deployment/development/docker-compose.yml b/deployment/development/docker-compose.yml index acccd8a4..588a39e6 100644 --- a/deployment/development/docker-compose.yml +++ b/deployment/development/docker-compose.yml @@ -37,3 +37,19 @@ services: DATABASE_URL: "postgresql://db-user:1234@db:5432/dev-buildengine?schema=public" # MUST be included (the path the application will be accessed on) and MUST NOT have a trailing slash ORIGIN: "http://localhost:8443" + + otel: + image: otel/opentelemetry-collector-contrib:0.136.0 + ports: + - "6317:6317" # gRPC + - "6318:6318" # HTTP + - "55679:55679" # UI + volumes: + - ./src/lib/otel/development_config.yml:/etc/development_config.yml + - ./src/lib/otel/receivers_config.yml:/etc/receivers_config.yml + command: ["--config", "/etc/development_config.yml", "--config", "/etc/receivers_config.yml"] + + jaeger: + image: cr.jaegertracing.io/jaegertracing/jaeger:latest + ports: + - "16686:16686" diff --git a/package-lock.json b/package-lock.json index 3929dfe7..1d22a78f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@aws-sdk/client-s3": "^3.907.0", "@aws-sdk/client-sts": "^3.907.0", "@prisma/client": "^6.15.0", + "bullmq-otel": "^1.1.1", "prisma": "^6.15.0", "s3-sync-client": "^4.3.1" }, @@ -26,6 +27,16 @@ "@eslint/js": "^9.18.0", "@hono/node-server": "^1.19.5", "@iconify/svelte": "^5.0.2", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.211.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.211.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.211.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/resources": "^2.5.0", + "@opentelemetry/sdk-logs": "^0.211.0", + "@opentelemetry/sdk-metrics": "^2.5.0", + "@opentelemetry/sdk-node": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.39.0", "@sveltejs/adapter-node": "^5.2.12", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -3866,6 +3877,39 @@ "license": "MIT", "optional": true }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3977,7 +4021,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/fs-minipass": { @@ -4043,6 +4086,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -4050,7 +4104,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4064,7 +4117,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4078,7 +4130,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4092,7 +4143,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4106,7 +4156,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4120,7 +4169,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4165,6 +4213,570 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz", + "integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.211.0.tgz", + "integrity": "sha512-PNsCkzsYQKyv8wiUIsH+loC4RYyblOaDnVASBtKS22hK55ToWs2UP6IsrcfSWWn54wWTvVe2gnfwz67Pvrxf2Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz", + "integrity": "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", + "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-UhOoWENNqyaAMP/dL1YXLkXt6ZBtovkDDs1p4rxto9YwJX1+wMjwg+Obfyg2kwpcMoaiIFT3KQIcLNW8nNGNfQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.211.0.tgz", + "integrity": "sha512-c118Awf1kZirHkqxdcF+rF5qqWwNjJh+BB1CmQvN9AQHC/DUIldy6dIkJn3EKlQnQ3HmuNRKc/nHHt5IusN7mA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/sdk-logs": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.211.0.tgz", + "integrity": "sha512-kMvfKMtY5vJDXeLnwhrZMEwhZ2PN8sROXmzacFU/Fnl4Z79CMrOaL7OE+5X3SObRYlDUa7zVqaXp9ZetYCxfDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-D/U3G8L4PzZp8ot5hX9wpgbTymgtLZCiwR7heMe4LsbGV4OdctS1nfyvaQHLT6CiGZ6FjKc1Vk9s6kbo9SWLXQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.211.0.tgz", + "integrity": "sha512-lfHXElPAoDSPpPO59DJdN5FLUnwi1wxluLTWQDayqrSPfWRnluzxRhD+g7rF8wbj1qCz0sdqABl//ug1IZyWvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.211.0.tgz", + "integrity": "sha512-61iNbffEpyZv/abHaz3BQM3zUtA2kVIDBM+0dS9RK68ML0QFLRGYa50xVMn2PYMToyfszEPEgFC3ypGae2z8FA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.211.0.tgz", + "integrity": "sha512-cD0WleEL3TPqJbvxwz5MVdVJ82H8jl8mvMad4bNU24cB5SH2mRW5aMLDTuV4614ll46R//R3RMmci26mc2L99g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-metrics": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.211.0.tgz", + "integrity": "sha512-eFwx4Gvu6LaEiE1rOd4ypgAiWEdZu7Qzm2QNN2nJqPW1XDeAVH1eNwVcVQl+QK9HR/JCDZ78PZgD7xD/DBDqbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.211.0.tgz", + "integrity": "sha512-F1Rv3JeMkgS//xdVjbQMrI3+26e5SXC7vXA6trx8SWEA0OUhw4JHB+qeHtH0fJn46eFItrYbL5m8j4qi9Sfaxw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.211.0.tgz", + "integrity": "sha512-DkjXwbPiqpcPlycUojzG2RmR0/SIK8Gi9qWO9znNvSqgzrnAIE9x2n6yPfpZ+kWHZGafvsvA1lVXucTyyQa5Kg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.5.0.tgz", + "integrity": "sha512-bk9VJgFgUAzkZzU8ZyXBSWiUGLOM3mZEgKJ1+jsZclhRnAoDNf+YBdq+G9R3cP0+TKjjWad+vVrY/bE/vRR9lA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz", + "integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.211.0.tgz", + "integrity": "sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.211.0.tgz", + "integrity": "sha512-mR5X+N4SuphJeb7/K7y0JNMC8N1mB6gEtjyTLv+TSAhl0ZxNQzpSKP8S5Opk90fhAqVYD4R0SQSAirEBlH1KSA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/otlp-exporter-base": "0.211.0", + "@opentelemetry/otlp-transformer": "0.211.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.211.0.tgz", + "integrity": "sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "protobufjs": "8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/protobufjs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.0.tgz", + "integrity": "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.5.0.tgz", + "integrity": "sha512-g10m4KD73RjHrSvUge+sUxUl8m4VlgnGc6OKvo68a4uMfaLjdFU+AULfvMQE/APq38k92oGUxEzBsAZ8RN/YHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.5.0.tgz", + "integrity": "sha512-t70ErZCncAR/zz5AcGkL0TF25mJiK1FfDPEQCgreyAHZ+mRJ/bNUiCnImIBDlP3mSDXy6N09DbUEKq0ktW98Hg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz", + "integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.211.0.tgz", + "integrity": "sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz", + "integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.211.0.tgz", + "integrity": "sha512-+s1eGjoqmPCMptNxcJJD4IxbWJKNLOQFNKhpwkzi2gLkEbCj6LzSHJNhPcLeBrBlBLtlSpibM+FuS7fjZ8SSFQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.211.0", + "@opentelemetry/configuration": "0.211.0", + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-logs-otlp-http": "0.211.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.211.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.211.0", + "@opentelemetry/exporter-prometheus": "0.211.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.211.0", + "@opentelemetry/exporter-trace-otlp-http": "0.211.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.211.0", + "@opentelemetry/exporter-zipkin": "2.5.0", + "@opentelemetry/instrumentation": "0.211.0", + "@opentelemetry/propagator-b3": "2.5.0", + "@opentelemetry/propagator-jaeger": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/sdk-logs": "0.211.0", + "@opentelemetry/sdk-metrics": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0", + "@opentelemetry/sdk-trace-node": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz", + "integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.5.0", + "@opentelemetry/resources": "2.5.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.0.tgz", + "integrity": "sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.5.0", + "@opentelemetry/core": "2.5.0", + "@opentelemetry/sdk-trace-base": "2.5.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz", + "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -4272,6 +4884,80 @@ "@prisma/debug": "6.15.0" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@rollup/plugin-commonjs": { "version": "28.0.6", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", @@ -6514,6 +7200,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6541,6 +7237,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6800,7 +7506,6 @@ "version": "5.61.0", "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.61.0.tgz", "integrity": "sha512-khaTjc1JnzaYFl4FrUtsSsqugAW/urRrcZ9Q0ZE+REAw8W+gkHFqxbGlutOu6q7j7n91wibVaaNlOUMdiEvoSQ==", - "dev": true, "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", @@ -6812,6 +7517,23 @@ "uuid": "^11.1.0" } }, + "node_modules/bullmq-otel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bullmq-otel/-/bullmq-otel-1.1.1.tgz", + "integrity": "sha512-AySECCquHi/wE4kQUVVUr55I0CNVJDD7cisATTkUX21rBgphOf3qraoqCIllsxAQ32OJ0FtaTvZOrV2APrNgoA==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "bullmq": "^5.22.0", + "tslib": "^2.8.0" + } + }, + "node_modules/bullmq-otel/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/c12": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", @@ -6965,6 +7687,13 @@ "consola": "^3.2.3" } }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/class-validator": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", @@ -6978,6 +7707,21 @@ "validator": "^13.9.0" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -6992,7 +7736,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=0.10.0" @@ -7061,7 +7804,6 @@ "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", - "dev": true, "license": "MIT", "dependencies": { "luxon": "^3.2.1" @@ -7174,7 +7916,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7260,7 +8001,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=0.10" @@ -7276,7 +8016,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -7363,6 +8103,13 @@ "node": ">=0.10.0" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/empathic": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", @@ -7595,6 +8342,16 @@ "esbuild": "*" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -8273,6 +9030,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -8562,6 +9329,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -8591,7 +9371,6 @@ "version": "5.8.2", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", - "dev": true, "license": "MIT", "dependencies": { "@ioredis/commands": "1.4.0", @@ -8773,6 +9552,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", @@ -9476,18 +10265,23 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { @@ -9497,11 +10291,17 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/luxon": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -9633,6 +10433,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "dev": true, + "license": "MIT" + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -9657,14 +10464,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/msgpackr": { "version": "1.11.5", "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", - "dev": true, "license": "MIT", "optionalDependencies": { "msgpackr-extract": "^3.0.2" @@ -9674,7 +10479,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -9723,7 +10527,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true, "license": "MIT" }, "node_modules/node-fetch-native": { @@ -9736,7 +10539,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -10352,6 +11154,31 @@ "license": "MIT", "optional": true }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10426,7 +11253,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10446,7 +11272,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "dev": true, "license": "MIT", "dependencies": { "redis-errors": "^1.0.0" @@ -10499,6 +11324,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -10691,7 +11540,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10922,7 +11770,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "dev": true, "license": "MIT" }, "node_modules/stop-iteration-iterator": { @@ -10939,6 +11786,21 @@ "node": ">= 0.4" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -10998,6 +11860,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11432,7 +12307,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true, "license": "0BSD" }, "node_modules/type-check": { @@ -11625,7 +12499,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -11871,6 +12744,34 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -11887,8 +12788,6 @@ "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", - "optional": true, - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -11896,6 +12795,35 @@ "node": ">= 14.6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b460c56c..47ae8283 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,16 @@ "@eslint/js": "^9.18.0", "@hono/node-server": "^1.19.5", "@iconify/svelte": "^5.0.2", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.211.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.211.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.211.0", + "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/resources": "^2.5.0", + "@opentelemetry/sdk-logs": "^0.211.0", + "@opentelemetry/sdk-metrics": "^2.5.0", + "@opentelemetry/sdk-node": "^0.211.0", + "@opentelemetry/semantic-conventions": "^1.39.0", "@sveltejs/adapter-node": "^5.2.12", "@sveltejs/kit": "^2.22.0", "@sveltejs/vite-plugin-svelte": "^6.0.0", @@ -58,6 +68,7 @@ "@aws-sdk/client-s3": "^3.907.0", "@aws-sdk/client-sts": "^3.907.0", "@prisma/client": "^6.15.0", + "bullmq-otel": "^1.1.1", "prisma": "^6.15.0", "s3-sync-client": "^4.3.1" }, diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6341d5ce..24e504af 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,6 +1,8 @@ -import { type Handle, error } from '@sveltejs/kit'; +import { SpanStatusCode, trace } from '@opentelemetry/api'; +import { type Handle, type HandleServerError, error } from '@sveltejs/kit'; import { sequence } from '@sveltejs/kit/hooks'; import { building } from '$app/environment'; +import OTEL from '$lib/otel'; import { tryVerifyAPIToken, tryVerifyCookie } from '$lib/server/auth'; import { QueueConnected, getQueues } from '$lib/server/bullmq'; import { bullboardHandle } from '$lib/server/bullmq/BullBoard'; @@ -25,6 +27,8 @@ const handleAuthRoute: Handle = async ({ event, resolve }) => { }; if (!building) { + // Start OTEL collector + OTEL.instance.start(); // Otherwise valkey will never connect and the server will always 503 getQueues(); // Likewise, initialize the Prisma connection heartbeat @@ -32,6 +36,7 @@ if (!building) { // Graceful shutdown process.on('sveltekit:shutdown', async () => { + OTEL.instance.logger.info('Shutting down gracefully...'); await Promise.all( allWorkers.map((worker) => { worker.worker?.close(); @@ -56,17 +61,102 @@ const heartbeat: Handle = async ({ event, resolve }) => { return resolve(event); }; +const tracer = trace.getTracer('IncomingRequest'); + +const authSequence: Handle = async ({ event, resolve }) => + event.route.id?.split('/')?.[1] === '(api)' + ? handleAPIRoute({ event, resolve }) + : handleAuthRoute({ event, resolve }); + export const handle: Handle = async ({ event, resolve }) => { if (event.url.pathname.startsWith('/.well-known/appspecific/')) { // Ignore these requests without logging them` return new Response('', { status: 404 }); } - return await sequence( - heartbeat, - (h) => { - return event.route.id?.split('/')?.[1] === '(api)' ? handleAPIRoute(h) : handleAuthRoute(h); - }, - bullboardHandle - )({ event, resolve }); + return tracer.startActiveSpan(`${event.request.method} ${event.url.pathname}`, async (span) => { + let clientIp; + try { + clientIp = event.getClientAddress(); + } catch (e) { + span.recordException(e as Error); + clientIp = 'unknown'; + } + span.setAttributes({ + 'http.method': event.request.method, + 'http.url': event.url.href, + 'http.route': event.url.pathname ?? '', + 'http.user_agent': event.request.headers.get('user-agent') ?? '', + 'http.client_ip': clientIp, + 'http.x-forwarded-for': event.request.headers.get('x-forwarded-for') ?? '', + 'svelte.route_id': event.route.id ?? '' + }); + try { + const response = await sequence( + heartbeat, + // Handle auth hooks in a separate OTEL span + (h) => { + return tracer.startActiveSpan('Authentication', async (span) => { + // Call the auth sequence + let spanEnded = false; + try { + const ret = await authSequence({ + event, + resolve: (...args) => { + if (!spanEnded) { + span.end(); + spanEnded = true; + } + return resolve(...args); + } + }); + return ret; + } finally { + if (!spanEnded) { + span.end(); + spanEnded = true; + } + } + }); + }, + bullboardHandle + )({ event, resolve }); + span.setAttributes({ + 'http.status_code': response.status + }); + return response; + } finally { + span.end(); + } + }); +}; + +export const handleError: HandleServerError = ({ error, event, status }) => { + // Log the error with OTEL + OTEL.instance.logger.error('Error in handleError', { + error: error instanceof Error ? error.message : String(error), + route: event.route.id, + method: event.request.method, + url: event.url.href + }); + trace.getActiveSpan()?.recordException(error as Error); + trace.getActiveSpan()?.setStatus({ + code: SpanStatusCode.ERROR, // Error + message: error instanceof Error ? error.message : String(error) + }); + + if (status === 404) { + // Don't log 404 errors, they are common and not actionable + return { + message: 'Not found', + status: 404 + }; + } + + console.error('Error occurred:', error); + + return { + message: 'An unexpected error occurred. Please try again later.', + status: 500 // Internal Server Error + }; }; diff --git a/src/lib/otel/development_config.yml b/src/lib/otel/development_config.yml new file mode 100644 index 00000000..24e46b24 --- /dev/null +++ b/src/lib/otel/development_config.yml @@ -0,0 +1,21 @@ +processors: + batch: +exporters: + debug: + # verbosity: detailed + otlp: + endpoint: jaeger:4317 + tls: + insecure: true +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [debug,otlp] + metrics: + receivers: [otlp] + exporters: [debug] + logs: + receivers: [otlp] + exporters: [debug] diff --git a/src/lib/otel/index.ts b/src/lib/otel/index.ts new file mode 100644 index 00000000..6d75e275 --- /dev/null +++ b/src/lib/otel/index.ts @@ -0,0 +1,114 @@ +// This file should ideally be imported first in the application to ensure +// that OpenTelemetry is initialized before any other modules are loaded. + +// Adding OTEL instrumentation hooks breaks the build +// It should look like this but breaks imports later + +// import { register } from 'node:module'; +// // eslint-disable-next-line import/order +// import { pathToFileURL } from 'node:url'; +// register('@opentelemetry/instrumentation/hook.mjs', pathToFileURL('./')); + +import { context } from '@opentelemetry/api'; +import type { AnyValueMap, Logger as OTLogger } from '@opentelemetry/api-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc'; +import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; +import { resourceFromAttributes } from '@opentelemetry/resources'; +import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs'; +import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'; + +class Logger { + constructor( + private logger: OTLogger, + private enableDev: boolean + ) {} + info(message: string, attributes: AnyValueMap = {}) { + this.logger.emit({ + body: message, + severityText: 'INFO', + attributes, + context: context.active() + }); + } + dev(message: string, attributes: AnyValueMap = {}) { + if (this.enableDev) { + this.logger.emit({ + body: message, + severityText: 'INFO - DEV', + attributes, + context: context.active() + }); + } + } + error(message: string, attributes: AnyValueMap = {}) { + this.logger.emit({ + body: message, + severityText: 'ERROR', + attributes, + context: context.active() + }); + } +} + +export default class OTEL { + private static _instance: OTEL; + private sdk: NodeSDK; + private initialized = false; + private _logger: Logger; + + private constructor() { + const isDev = process.env.NODE_ENV === 'development'; + const endpoint = `http://${isDev ? 'localhost' : 'otel'}:6317`; + + const resource = resourceFromAttributes({ + [ATTR_SERVICE_NAME]: process.env.APP_ENV + '_build-engine', + [ATTR_SERVICE_VERSION]: '1.0' + }); + + const logProcessor = new SimpleLogRecordProcessor( + new OTLPLogExporter({ + url: endpoint + }) + ); + + this._logger = new Logger( + new LoggerProvider({ + resource, + processors: [logProcessor] + }).getLogger(process.env.APP_ENV + '_build-engine-logger'), + isDev + ); + + this.sdk = new NodeSDK({ + resource, + traceExporter: new OTLPTraceExporter({ url: endpoint }), + metricReaders: [ + new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter({ url: endpoint }) + }) + ], + logRecordProcessors: [logProcessor] + }); + } + + public static get instance(): OTEL { + if (!OTEL._instance) { + OTEL._instance = new OTEL(); + } + return OTEL._instance; + } + + public start() { + if (!this.initialized) { + this.initialized = true; + this.sdk.start(); + } + } + + public get logger() { + return this._logger; + } +} diff --git a/src/lib/otel/production_config.yml b/src/lib/otel/production_config.yml new file mode 100644 index 00000000..21bd4c94 --- /dev/null +++ b/src/lib/otel/production_config.yml @@ -0,0 +1,123 @@ +processors: + memory_limiter: + check_interval: 5s + limit_mib: 500 + spike_limit_mib: 100 + batch: + # Add SampleRate attribute to traces for Honeycomb automatic accounting + # SampleRate = N where we keep 1/N traces, or (sampled traces)/(real traces) + # Higher N means lower sampling rate + # N = 4 means 1/4 = 25% of traces sampled + # N = 2 means 1/2 = 50% of traces sampled + # N = 1.25 means 1/1.25 = 80% of traces sampled + # N = 1 means 100% of traces sampled + attributes/noisy: + actions: + - key: SampleRate + action: insert + value: 2 + attributes/default: + actions: + - key: SampleRate + action: insert + value: 1.25 + attributes/forced: + actions: + - key: SampleRate + action: insert + value: 1 + tail_sampling: + decision_wait: 10s + num_traces: 1000 + policies: + # Keep 5% of /admin/jobs requests (noisy route) + - name: Throttle noisy routes + type: and + and: + and_sub_policy: + - name: Match noisy routes + type: string_attribute + string_attribute: + key: svelte.route_id + values: ["/(ui)/queue-admin/[...rest]"] + - name: Throttle noisy routes + type: probabilistic + probabilistic: + sampling_percentage: 5.0 + # Sample 80% of all traces + - name: Sample 80% of all traces + type: probabilistic + probabilistic: + sampling_percentage: 80.0 + # Keep all error traces + - name: Keep all error traces + type: status_code + status_code: { status_codes: [ERROR] } + # Keep traces with force_sample + - name: Keep traces with force_sample + type: boolean_attribute + boolean_attribute: + key: force_sample + value: true + # Drop traces with do_not_sample + - name: Drop traces with do_not_sample + type: drop + drop: + drop_sub_policy: + - type: boolean_attribute + boolean_attribute: + key: do_not_sample + value: true + +connectors: + routing: + default_pipelines: [traces/default] + table: + - context: span + condition: resource.attributes["svelte.route_id"] == "/(ui)/queue-admin/[...rest]" + pipelines: [traces/noisy] + - context: span + condition: resource.attributes["force_sample"] == true or span.status.code == STATUS_CODE_ERROR + pipelines: [traces/forced] + +exporters: + debug: + otlp: + endpoint: api.honeycomb.io:443 + headers: + 'x-honeycomb-team': ${env:HONEYCOMB_API_KEY} + otlp/metrics: + endpoint: api.honeycomb.io:443 + headers: + 'x-honeycomb-team': ${env:HONEYCOMB_API_KEY} + 'x-honeycomb-dataset': 'appbuilder-buildengine-api-metrics' + otlp/logs: + endpoint: api.honeycomb.io:443 + headers: + 'x-honeycomb-team': ${env:HONEYCOMB_API_KEY} + 'x-honeycomb-dataset': 'appbuilder-buildengine-api-logs' + +service: + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, tail_sampling] + exporters: [routing] + traces/noisy: + receivers: [routing] + processors: [attributes/noisy, batch] + exporters: [debug, otlp] + traces/default: + receivers: [routing] + processors: [attributes/default, batch] + exporters: [debug, otlp] + traces/forced: + receivers: [routing] + processors: [attributes/forced, batch] + exporters: [debug, otlp] + metrics: + receivers: [otlp] + exporters: [debug, otlp/metrics] + logs: + receivers: [otlp] + exporters: [debug, otlp/logs] diff --git a/src/lib/otel/receivers_config.yml b/src/lib/otel/receivers_config.yml new file mode 100644 index 00000000..d6cfe165 --- /dev/null +++ b/src/lib/otel/receivers_config.yml @@ -0,0 +1,7 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:6317 + http: + endpoint: 0.0.0.0:6318 diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index c3efca22..1b8c5811 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -1,3 +1,4 @@ +import { trace } from '@opentelemetry/api'; import type { Prisma } from '@prisma/client'; import { type RequestEvent, error, redirect } from '@sveltejs/kit'; import { jwtDecrypt } from 'jose'; @@ -17,6 +18,8 @@ export async function tryVerifyCookie(event: RequestEvent, gotoLoginPage = true) token = await jwtDecrypt(cookie, new TextEncoder().encode(secrets.AUTH0_SECRET)); event.locals.userEmail = token.payload.email as string; + + trace.getActiveSpan()?.setAttribute('user.email', event.locals.userEmail); } } catch { /* empty */ @@ -41,6 +44,11 @@ async function initiateScriptoriaLogin(event: RequestEvent) { const verify = randomUUID(); const requestId = randomUUID(); + trace.getActiveSpan()?.setAttributes({ + 'scriptoria-endpoint': env.PUBLIC_SCRIPTORIA_URL, + 'request-id': requestId + }); + await getAuthConnection().set(`${requestId}`, verify, 'EX', 300); // 5 minute (300 s) TTL const hash = createHash('sha256'); @@ -98,5 +106,7 @@ export async function tryVerifyAPIToken( return [false, ErrorResponse(403, 'Invalid Access Token')]; } + trace.getActiveSpan()?.setAttributes({ 'client.id': client.id, 'client.prefix': client.prefix }); + return [true, client]; } diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index 7327889e..a8693dab 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -8,6 +8,7 @@ import { type ProjectSource, StartBuildCommand } from '@aws-sdk/client-codebuild'; +import { SpanStatusCode, trace } from '@opentelemetry/api'; import type { Prisma } from '@prisma/client'; import { AWSCommon } from './common'; import { S3 } from './s3'; @@ -18,7 +19,6 @@ import { getBasePrefixUrl } from '$lib/server/models/artifacts'; import { Job } from '$lib/server/models/job'; -import { Utils } from '$lib/server/utils'; export type ReleaseForCodeBuild = Prisma.releaseGetPayload<{ select: { @@ -45,6 +45,8 @@ export type BuildForCodeBuild = Prisma.buildGetPayload<{ }; }>; +const tracer = trace.getTracer('CodeBuild'); + export class CodeBuild extends AWSCommon { public codeBuildClient; public constructor() { @@ -78,39 +80,80 @@ export class CodeBuild extends AWSCommon { versionCode: number, codeCommit: boolean ) { - const prefix = Utils.getPrefix(); - const job = build.job; - const buildProcess = `build_${job.app_id}`; - const jobNumber = String(job.id); - const buildNumber = String(build.id); - console.log( - `[${prefix}] startBuild CodeBuild Project: ${buildProcess} URL: ${repoUrl} commitId: ${commitId} jobNumber: ${jobNumber} buildNumber: ${buildNumber} versionCode: ${versionCode}` - ); - const artifacts_bucket = CodeBuild.getArtifactsBucket(); - const secretsBucket = CodeBuild.getSecretsBucket(); - const buildApp = CodeBuild.getCodeBuildProjectName('build_app'); - const buildPath = this.getBuildPath(job); - const artifactPath = getArtifactPath(job, 'codebuild-output'); - console.log(`Artifacts path: ${artifactPath}`); - // Leaving all this code together to make it easier to remove when git is no longer supported - if (codeCommit) { - console.log(`[${prefix}] startBuild CodeCommit Project`); - const res = await this.codeBuildClient.send( - new StartBuildCommand({ - projectName: buildApp, - artifactsOverride: { - location: artifacts_bucket, - name: '/', - namespaceType: 'NONE', - packaging: 'NONE', - path: artifactPath, - type: 'S3' - }, - buildspecOverride: buildSpec, - environmentVariablesOverride: [ + return tracer.startActiveSpan(`CodeBuild - StartBuild`, async (span) => { + try { + const job = build.job; + const buildProcess = `build_${job.app_id}`; + span.setAttributes({ + 'code-build.process': buildProcess, + 'code-build.repo-url': repoUrl, + 'code-build.commit-id': commitId, + 'code-build.job-id': job.id, + 'code-build.build-id': build.id, + 'code-build.version-code': versionCode + }); + const artifacts_bucket = CodeBuild.getArtifactsBucket(); + const secretsBucket = CodeBuild.getSecretsBucket(); + const buildApp = CodeBuild.getCodeBuildProjectName('build_app'); + const buildPath = this.getBuildPath(job); + const artifactPath = getArtifactPath(job, 'codebuild-output'); + span.setAttribute('code-build.artifact-path', artifactPath); + // Leaving all this code together to make it easier to remove when git is no longer supported + if (codeCommit) { + span.addEvent('StartBuild - CodeCommit'); + const res = await this.codeBuildClient.send( + new StartBuildCommand({ + projectName: buildApp, + artifactsOverride: { + location: artifacts_bucket, + name: '/', + namespaceType: 'NONE', + packaging: 'NONE', + path: artifactPath, + type: 'S3' + }, + buildspecOverride: buildSpec, + environmentVariablesOverride: [ + { + name: 'BUILD_NUMBER', + value: String(build.id) + }, + { + name: 'APP_BUILDER_SCRIPT_PATH', + value: buildPath + }, + { + name: 'PUBLISHER', + value: job.publisher_id + }, + { + name: 'VERSION_CODE', + value: '' + versionCode + }, + { + name: 'SECRETS_BUCKET', + value: secretsBucket + } + ], + sourceLocationOverride: repoUrl, + sourceVersion: commitId + }) + ); + const buildId = res.build?.id; + const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); + span.setAttributes({ + 'code-build.build.build-id': buildId, + 'code-build.build.build-guid': buildGuid + }); + return buildGuid; + } else { + span.addEvent('StartBuild - S3'); + const targets = build.targets ?? 'apk play-listing'; + const environmentArray = [ + // BUILD_NUMBER Must be first for tests { name: 'BUILD_NUMBER', - value: buildNumber + value: String(build.id) }, { name: 'APP_BUILDER_SCRIPT_PATH', @@ -127,83 +170,62 @@ export class CodeBuild extends AWSCommon { { name: 'SECRETS_BUCKET', value: secretsBucket + }, + { + name: 'PROJECT_S3', + value: repoUrl + }, + { + name: 'TARGETS', + value: targets + }, + { + name: 'SCRIPT_S3', + value: S3.getBuildScriptPath() } - ], - sourceLocationOverride: repoUrl, - sourceVersion: commitId - }) - ); - const buildId = res.build?.id; - const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); - console.log(`Build id: ${buildId} Guid: ${buildGuid}`); - return buildGuid; - } else { - console.log(`[${prefix}] startBuild S3 Project`); - const targets = build.targets ?? 'apk play-listing'; - const environmentArray = [ - // BUILD_NUMBER Must be first for tests - { - name: 'BUILD_NUMBER', - value: buildNumber - }, - { - name: 'APP_BUILDER_SCRIPT_PATH', - value: buildPath - }, - { - name: 'PUBLISHER', - value: job.publisher_id - }, - { - name: 'VERSION_CODE', - value: '' + versionCode - }, - { - name: 'SECRETS_BUCKET', - value: secretsBucket - }, - { - name: 'PROJECT_S3', - value: repoUrl - }, - { - name: 'TARGETS', - value: targets - }, - { - name: 'SCRIPT_S3', - value: S3.getBuildScriptPath() + ]; + const adjustedEnvironmentArray = this.addEnvironmentToArray( + environmentArray, + build.environment + ); + const computeType = this.getComputeType(adjustedEnvironmentArray); + const imageTag = this.getImageTag(adjustedEnvironmentArray); + const result = await this.codeBuildClient.send( + new StartBuildCommand({ + projectName: buildApp, + artifactsOverride: { + location: artifacts_bucket, // output bucket + name: '/', // name of output artifact object + namespaceType: 'NONE', + packaging: 'NONE', + path: artifactPath, // path to output artifacts + type: 'S3' // REQUIRED + }, + buildspecOverride: buildSpec, + environmentVariablesOverride: adjustedEnvironmentArray, + sourceTypeOverride: 'NO_SOURCE', + computeTypeOverride: computeType, + imageOverride: CodeBuild.getCodeBuildImageRepo() + ':' + imageTag + }) + ); + const buildId = result.build?.id; + const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); + span.setAttributes({ + 'code-build.build.build-id': buildId, + 'code-build.build.build-guid': buildGuid + }); + return buildGuid; } - ]; - const adjustedEnvironmentArray = this.addEnvironmentToArray( - environmentArray, - build.environment - ); - const computeType = this.getComputeType(adjustedEnvironmentArray); - const imageTag = this.getImageTag(adjustedEnvironmentArray); - const result = await this.codeBuildClient.send( - new StartBuildCommand({ - projectName: buildApp, - artifactsOverride: { - location: artifacts_bucket, // output bucket - name: '/', // name of output artifact object - namespaceType: 'NONE', - packaging: 'NONE', - path: artifactPath, // path to output artifacts - type: 'S3' // REQUIRED - }, - buildspecOverride: buildSpec, - environmentVariablesOverride: adjustedEnvironmentArray, - sourceTypeOverride: 'NO_SOURCE', - computeTypeOverride: computeType, - imageOverride: CodeBuild.getCodeBuildImageRepo() + ':' + imageTag - }) - ); - const buildId = result.build?.id; - const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); - console.log(`Build id: ${buildId} Guid: ${buildGuid}`); - return buildGuid; - } + } catch (e) { + span.recordException(e as Error); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } finally { + span.end(); + } + }); } /** @@ -214,16 +236,27 @@ export class CodeBuild extends AWSCommon { * @return AWS/Result Result object on the status of the build */ public async getBuildStatus(guid: string, buildProcess: string) { - const prefix = Utils.getPrefix(); - console.log(`[${prefix}] getBuildStatus CodeBuild Project: ${buildProcess} BuildGuid: ${guid}`); + const span = trace.getActiveSpan(); + try { + span?.setAttributes({ + 'code-build.build-process': buildProcess, + 'code-build.build-guid': guid + }); - const buildId = this.getBuildId(guid, buildProcess); - const result = await this.codeBuildClient.send( - new BatchGetBuildsCommand({ - ids: [buildId] - }) - ); - return result.builds?.at(0); + const buildId = this.getBuildId(guid, buildProcess); + const result = await this.codeBuildClient.send( + new BatchGetBuildsCommand({ + ids: [buildId] + }) + ); + return result.builds?.at(0); + } catch (e) { + span?.recordException(e as Error); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } } /** @@ -249,9 +282,7 @@ export class CodeBuild extends AWSCommon { * @return string CodeBuild build arn */ private getBuildId(guid: string, buildProcess: string) { - const buildId = `${buildProcess}:${guid}`; - console.log(`getBuildId arn: ${buildId}`); - return buildId; + return `${buildProcess}:${guid}`; } /** * Returns the name of the shell command to be run @@ -281,99 +312,119 @@ export class CodeBuild extends AWSCommon { * @return false|string */ public async startRelease(release: ReleaseForCodeBuild, releaseSpec: string) { - console.log('startRelease: '); - const releaseNumber = '' + release.id; - const build = release.build; - const buildNumber = '' + build.id; - const job = build.job; - const buildPath = this.getBuildPath(job); - const artifacts_bucket = CodeBuild.getArtifactsBucket(); - const artifactPath = getArtifactPath(job, 'codebuild-output', true); - const secretsBucket = CodeBuild.getSecretsBucket(); - const scriptureEarthKey = CodeBuild.getScriptureEarthKey(); - const publishApp = CodeBuild.getCodeBuildProjectName('publish_app'); - const promoteFrom = release.promote_from ?? ''; + return tracer.startActiveSpan(`CodeBuild - StartRelease`, async (span) => { + try { + const build = release.build; + const job = build.job; + const buildPath = this.getBuildPath(job); + const artifacts_bucket = CodeBuild.getArtifactsBucket(); + const artifactPath = getArtifactPath(job, 'codebuild-output', true); + const secretsBucket = CodeBuild.getSecretsBucket(); + const scriptureEarthKey = CodeBuild.getScriptureEarthKey(); + const publishApp = CodeBuild.getCodeBuildProjectName('publish_app'); + const promoteFrom = release.promote_from ?? ''; - const sourceLocation = this.getSourceLocation(build); - const s3Artifacts = this.getArtifactsLocation(build); - console.log(`Source location: ${sourceLocation}`); - const targets = release.targets ?? 'google-play'; + const sourceLocation = this.getSourceLocation(build); + const s3Artifacts = this.getArtifactsLocation(build); + const targets = release.targets ?? 'google-play'; - const environmentArray = [ - // RELEASE_NUMBER Must be first for tests - { - name: 'RELEASE_NUMBER', - value: releaseNumber - }, - { - name: 'APP_BUILDER_SCRIPT_PATH', - value: buildPath - }, - { - name: 'BUILD_NUMBER', - value: buildNumber - }, - { - name: 'CHANNEL', - value: release.channel - }, - { - name: 'PUBLISHER', - value: job.publisher_id - }, - { - name: 'PROJECT_S3', - value: job.git_url - }, - { - name: 'SECRETS_BUCKET', - value: secretsBucket - }, - { - name: 'PROMOTE_FROM', - value: promoteFrom - }, - { - name: 'ARTIFACTS_S3_DIR', - value: s3Artifacts - }, - { - name: 'TARGETS', - value: targets - }, - { - name: 'SCRIPT_S3', - value: S3.getBuildScriptPath() - }, - { - name: 'SCRIPTURE_EARTH_KEY', - value: scriptureEarthKey + span.setAttributes({ + 'code-build.release-id': release.id, + 'code-build.build-id': build.id, + 'code-build.artifact-path': artifactPath, + 'code-build.promote-from': promoteFrom, + 'code-build.source': sourceLocation, + 'code-build.targets': targets + }); + + const environmentArray = [ + // RELEASE_NUMBER Must be first for tests + { + name: 'RELEASE_NUMBER', + value: String(release.id) + }, + { + name: 'APP_BUILDER_SCRIPT_PATH', + value: buildPath + }, + { + name: 'BUILD_NUMBER', + value: String(build.id) + }, + { + name: 'CHANNEL', + value: release.channel + }, + { + name: 'PUBLISHER', + value: job.publisher_id + }, + { + name: 'PROJECT_S3', + value: job.git_url + }, + { + name: 'SECRETS_BUCKET', + value: secretsBucket + }, + { + name: 'PROMOTE_FROM', + value: promoteFrom + }, + { + name: 'ARTIFACTS_S3_DIR', + value: s3Artifacts + }, + { + name: 'TARGETS', + value: targets + }, + { + name: 'SCRIPT_S3', + value: S3.getBuildScriptPath() + }, + { + name: 'SCRIPTURE_EARTH_KEY', + value: scriptureEarthKey + } + ]; + const adjustedEnvironmentArray = this.addEnvironmentToArray( + environmentArray, + release.environment + ); + const result = await this.codeBuildClient.send( + new StartBuildCommand({ + projectName: publishApp, + artifactsOverride: { + location: artifacts_bucket, // output bucket + name: '/', // name of output artifact object + namespaceType: 'NONE', + packaging: 'NONE', + path: artifactPath, // path to output artifacts + type: 'S3' // REQUIRED + }, + buildspecOverride: releaseSpec, + environmentVariablesOverride: adjustedEnvironmentArray, + sourceLocationOverride: sourceLocation + }) + ); + const buildId = result.build?.id; + const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); + span.setAttributes({ + 'code-build.release.build-id': buildId, + 'code-build.release.build-guid': buildGuid + }); + return buildGuid; + } catch (e) { + span.recordException(e as Error); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } finally { + span.end(); } - ]; - const adjustedEnvironmentArray = this.addEnvironmentToArray( - environmentArray, - release.environment - ); - const result = await this.codeBuildClient.send( - new StartBuildCommand({ - projectName: publishApp, - artifactsOverride: { - location: artifacts_bucket, // output bucket - name: '/', // name of output artifact object - namespaceType: 'NONE', - packaging: 'NONE', - path: artifactPath, // path to output artifacts - type: 'S3' // REQUIRED - }, - buildspecOverride: releaseSpec, - environmentVariablesOverride: adjustedEnvironmentArray, - sourceLocationOverride: sourceLocation - }) - ); - const buildId = result.build?.id; - const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); - console.log(`Build id: ${buildId} Guid: ${buildGuid}`); - return buildGuid; + }); } /** * Get the url for the apk file in a format that codebuild accepts for an S3 Source @@ -422,7 +473,6 @@ export class CodeBuild extends AWSCommon { ) { const project_name = CodeBuild.getCodeBuildProjectName(base_name); const artifacts_bucket = CodeBuild.getArtifactsBucket(); - console.log(`Bucket: ${artifacts_bucket}`); return await this.codeBuildClient.send( new CreateProjectCommand({ artifacts: { @@ -470,14 +520,24 @@ export class CodeBuild extends AWSCommon { * @return boolean true if project found */ public async projectExists(baseName: string) { - const projectName = CodeBuild.getCodeBuildProjectName(baseName); - console.log(`Check project ${projectName} exists`); - const result = await this.codeBuildClient.send( - new BatchGetProjectsCommand({ - names: [projectName] - }) - ); - return !!result.projects?.length; + let exists = false; + try { + const projectName = CodeBuild.getCodeBuildProjectName(baseName); + trace?.getActiveSpan()?.setAttribute('code-build.project-name', projectName); + const result = await this.codeBuildClient.send( + new BatchGetProjectsCommand({ + names: [projectName] + }) + ); + exists = !!result.projects?.length; + } catch (e) { + trace.getActiveSpan()?.recordException(e as Error); + trace.getActiveSpan()?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } + return exists; } private addEnvironmentToArray( @@ -498,7 +558,7 @@ export class CodeBuild extends AWSCommon { ) ); } catch { - console.log('Exception caught and ignored'); + /* empty */ } } return environmentVariables; diff --git a/src/lib/server/aws/codecommit.ts b/src/lib/server/aws/codecommit.ts index 03056862..1d44a640 100644 --- a/src/lib/server/aws/codecommit.ts +++ b/src/lib/server/aws/codecommit.ts @@ -3,8 +3,8 @@ import { GetBranchCommand, GetRepositoryCommand } from '@aws-sdk/client-codecommit'; +import { SpanStatusCode, trace } from '@opentelemetry/api'; import { AWSCommon } from './common'; -import { Utils } from '$lib/server/utils'; export class CodeCommit extends AWSCommon { public codeCommitClient; @@ -18,9 +18,25 @@ export class CodeCommit extends AWSCommon { * @return \Aws\CodeBuild\CodeCommitClient */ public static getCodeCommitClient() { - return new CodeCommitClient({ - region: AWSCommon.getArtifactsBucketRegion() - }); + const span = trace.getActiveSpan(); + let client: CodeCommitClient | null = null; + try { + client = new CodeCommitClient({ + region: AWSCommon.getArtifactsBucketRegion() + }); + } catch (e) { + span?.recordException(e as Error); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } finally { + span?.addEvent('CodeCommit - getCodeCommitClient', { + 'code-commit.client.api-version': client?.config.apiVersion, + 'code-commit.client.service-id': client?.config.serviceId + }); + } + return client!; } /** * Returns http url of code commit archive derived from git url needed for CodeBuild @@ -29,15 +45,28 @@ export class CodeCommit extends AWSCommon { * @return string http codecommit url */ public async getSourceURL(git_url: string) { - console.log(`[${Utils.getPrefix()}] getSourceURL URL: ${git_url}`); - const repo = git_url.substring(git_url.indexOf('/') + 1); - const repoInfo = await this.codeCommitClient.send( - new GetRepositoryCommand({ - repositoryName: repo - }) - ); - const cloneUrl = repoInfo.repositoryMetadata?.cloneUrlHttp; - console.log(`cloneUrl: ${cloneUrl}`); + const span = trace.getActiveSpan(); + let cloneUrl: string | undefined = undefined; + try { + const repo = git_url.substring(git_url.indexOf('/') + 1); + const repoInfo = await this.codeCommitClient.send( + new GetRepositoryCommand({ + repositoryName: repo + }) + ); + cloneUrl = repoInfo.repositoryMetadata?.cloneUrlHttp; + } catch (e) { + span?.recordException(e as Error); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } finally { + span?.addEvent('CodeCommit - getSourceURL', { + 'code-commit.git-url': git_url, + 'code-commit.source-url': cloneUrl ?? '' + }); + } return cloneUrl; } /** @@ -47,15 +76,28 @@ export class CodeCommit extends AWSCommon { * @return string http codecommit url */ public async getSourceSshURL(git_url: string) { - console.log(`[${Utils.getPrefix()}] getSourceURL URL: ${git_url}`); - const repo = git_url.substring(git_url.indexOf('/') + 1); - const repoInfo = await this.codeCommitClient.send( - new GetRepositoryCommand({ - repositoryName: repo - }) - ); - const cloneUrl = repoInfo.repositoryMetadata?.cloneUrlSsh; - console.log(`cloneUrl: ${cloneUrl}`); + const span = trace.getActiveSpan(); + let cloneUrl: string | undefined = undefined; + try { + const repo = git_url.substring(git_url.indexOf('/') + 1); + const repoInfo = await this.codeCommitClient.send( + new GetRepositoryCommand({ + repositoryName: repo + }) + ); + cloneUrl = repoInfo.repositoryMetadata?.cloneUrlSsh; + } catch (e) { + span?.recordException(e as Error); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } finally { + span?.addEvent('CodeCommit - getSourceSshURL', { + 'code-commit.git-url': git_url, + 'code-commit.source-ssh-url': cloneUrl ?? '' + }); + } return cloneUrl; } @@ -67,16 +109,29 @@ export class CodeCommit extends AWSCommon { * @return string commit id */ public async getCommitId(git_url: string, branch: string) { - console.log(`[${Utils.getPrefix()}] getCommitId URL: ${git_url} Branch: ${branch}`); - const repo = git_url.substring(git_url.indexOf('/') + 1); - const result = await this.codeCommitClient.send( - new GetBranchCommand({ - branchName: branch, - repositoryName: repo - }) - ); - const commitId = result.branch?.commitId; - console.log(`commitId: ${commitId}`); + const span = trace.getActiveSpan(); + let commitId: string | undefined = undefined; + try { + const repo = git_url.substring(git_url.indexOf('/') + 1); + const result = await this.codeCommitClient.send( + new GetBranchCommand({ + branchName: branch, + repositoryName: repo + }) + ); + commitId = result.branch?.commitId; + } catch (e) { + span?.recordException(e as Error); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } finally { + span?.addEvent('CodeCommit - getCommitId', { + 'code-commit.git-url': git_url, + 'code-commit.commit-id': commitId ?? '' + }); + } return commitId; } } diff --git a/src/lib/server/aws/iamwrapper.ts b/src/lib/server/aws/iamwrapper.ts index c90c02b2..8615dde3 100644 --- a/src/lib/server/aws/iamwrapper.ts +++ b/src/lib/server/aws/iamwrapper.ts @@ -12,6 +12,7 @@ import { RemoveUserFromGroupCommand, UploadSSHPublicKeyCommand } from '@aws-sdk/client-iam'; +import { trace } from '@opentelemetry/api'; import { AWSCommon } from './common'; import { env } from '$env/dynamic/private'; @@ -54,7 +55,6 @@ export class IAmWrapper extends AWSCommon { public async doesRoleExist(projectName: string) { try { const fullRoleName = AWSCommon.getRoleName(projectName); - console.log(`Check role ${fullRoleName} exists`); await this.iamClient.send( new GetRoleCommand({ RoleName: fullRoleName // REQUIRED @@ -79,7 +79,7 @@ export class IAmWrapper extends AWSCommon { }) ); const roleArn = result.Role?.Arn ?? ''; - console.log(`Role Arn is ${roleArn}`); + trace.getActiveSpan()?.setAttribute('iam.role-arn', roleArn); return roleArn; } catch { return ''; diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index dea3499f..48fc53c7 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -9,6 +9,7 @@ import { S3ServiceException, type _Object } from '@aws-sdk/client-s3'; +import { SpanStatusCode, trace } from '@opentelemetry/api'; import { basename, dirname, extname } from 'node:path'; import { S3SyncClient } from 's3-sync-client'; import { AWSCommon } from './common'; @@ -21,7 +22,6 @@ import { getBasePrefixUrl, handleArtifact } from '$lib/server/models/artifacts'; -import { Utils } from '$lib/server/utils'; export class S3 extends AWSCommon { public s3Client; @@ -80,17 +80,25 @@ export class S3 extends AWSCommon { ); fileContents = await result.Body!.transformToString(); } catch (e) { + const span = trace.getActiveSpan(); + span?.recordException(e as Error); // There is not a good way to check for file exists. If file doesn't exist, // it will be caught here and an empty string returned. if (e instanceof NoSuchKey) { - console.error( - `Error from S3 while getting object "${filePath}" from "${bucket}". No such key exists.` - ); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: `Error from S3 while getting object "${filePath}" from "${bucket}". No such key exists.` + }); } else if (e instanceof S3ServiceException) { - console.error( - `Error from S3 while getting object from ${bucket}. ${e.name}: ${e.message}` - ); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: `Error from S3 while getting object from ${bucket}. ${e.name}: ${e.message}` + }); } else { + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); throw e; } } @@ -106,18 +114,26 @@ export class S3 extends AWSCommon { * @param Build or Release artifacts_provider - The build or release */ public async copyS3Folder(artifacts_provider: ProviderForPrefix & ProviderForArtifacts) { + const span = trace.getActiveSpan(); const artifactsBucket = S3.getArtifactsBucket(); const destFolderPrefix = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()); const sourcePrefix = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; const destPrefix = destFolderPrefix + '/'; beginArtifacts(artifacts_provider, `https://${artifactsBucket}.s3.amazonaws.com/${destPrefix}`); + span?.addEvent(`S3 - Copy Folder`, { + 's3.bucket': artifactsBucket, + 's3.source': sourcePrefix, + 's3.dest': destPrefix + }); try { await this.deleteMatchingObjects(artifactsBucket, destFolderPrefix); } catch (e) { - if (e instanceof S3ServiceException) { - console.error(`[${[Utils.getPrefix()]}] copyS3Build - Folder: Exception:\n${e}`); - } else { + span?.recordException(e as Error); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + if (!(e instanceof S3ServiceException)) { throw e; } } @@ -153,22 +169,24 @@ export class S3 extends AWSCommon { destPrefix: string, artifacts_provider: ProviderForPrefix & ProviderForArtifacts ) { + const span = trace.getActiveSpan(); const artifactsBucket = S3.getArtifactsBucket(); let fileContents = ''; const fileNameWithPrefix = file['Key']!; const fileName = fileNameWithPrefix.substring(sourcePrefix.length); + span?.addEvent(`S3 - Copy File`, { + 's3.bucket': artifactsBucket, + 's3.source': sourcePrefix, + 's3.file': fileName + }); switch (fileName) { case 'manifest.txt': - return; case 'play-listing/default-language.txt': return; //case: 'version.json': FUTURE: get versionCode from version.json case 'version_code.txt': fileContents = await this.readS3File(artifacts_provider, fileName); break; - default: - console.log(fileName); - break; } const sourceDir = dirname(fileNameWithPrefix); const sourceBasename = basename(fileNameWithPrefix); @@ -187,9 +205,17 @@ export class S3 extends AWSCommon { ); return { file: fileName, contents: fileContents }; } catch (e) { + span?.recordException(e as Error); if (e instanceof Error) { - console.error(`File was not renamed ${sourceFile}\nexception: ${e.stack}`); + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: `File was not renamed ${sourceFile}\nexception: ${e.message}` + }); } else { + span?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); throw e; } } @@ -204,6 +230,12 @@ export class S3 extends AWSCommon { const destPrefix: string = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()); const fileS3Key = destPrefix + '/' + fileName; + trace.getActiveSpan()?.addEvent(`S3 - Write File`, { + 's3.bucket': fileS3Bucket, + 's3.dest': destPrefix, + 's3.file': fileName + }); + await this.s3Client.send( new PutObjectCommand({ Bucket: fileS3Bucket, @@ -218,11 +250,18 @@ export class S3 extends AWSCommon { public async removeCodeBuildFolder(artifacts_provider: ProviderForPrefix) { const s3Folder = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; const s3Bucket = S3.getArtifactsBucket(); - console.log(`Deleting S3 bucket: ${s3Bucket} key: ${s3Folder}`); + trace.getActiveSpan()?.addEvent(`S3 - Remove CodeBuild Folder`, { + 's3.bucket': s3Bucket, + 's3.folder': s3Folder + }); return await this.deleteMatchingObjects(s3Bucket, s3Folder); } public async uploadFolder(folderName: string, bucket: string) { + trace.getActiveSpan()?.addEvent(`S3 - Upload Folder`, { + 's3.bucket': bucket, + 's3.folder': folderName + }); const client = new S3SyncClient({ client: this.getS3ClientWithCredentials() }); return await client.sync(folderName, bucket); } @@ -247,6 +286,10 @@ export class S3 extends AWSCommon { } private async deleteMatchingObjects(Bucket: string, Prefix: string) { + trace.getActiveSpan()?.addEvent(`S3 - Delete Objects`, { + 's3.bucket': Bucket, + 's3.prefix': Prefix + }); // If destination folder already exists from some previous build, delete const existing = await this.s3Client.send( new ListObjectsV2Command({ diff --git a/src/lib/server/bullmq/BullWorker.ts b/src/lib/server/bullmq/BullWorker.ts index 394884eb..2a2a898a 100644 --- a/src/lib/server/bullmq/BullWorker.ts +++ b/src/lib/server/bullmq/BullWorker.ts @@ -1,10 +1,13 @@ -import type { Job } from 'bullmq'; +import { SpanStatusCode, trace } from '@opentelemetry/api'; +import type { Exception, Job } from 'bullmq'; import { Worker } from 'bullmq'; import * as Executor from '../job-executors'; import { getQueues, getWorkerConfig } from './queues'; import * as BullMQ from './types'; import { building } from '$app/environment'; +const tracer = trace.getTracer('BullWorker'); + export abstract class BullWorker { public worker?: Worker; constructor(public queue: BullMQ.QueueName) { @@ -13,11 +16,30 @@ export abstract class BullWorker { this.worker = new Worker(queue, this.runInternal.bind(this), getWorkerConfig()); } private async runInternal(job: Job) { - try { - return await this.run(job); - } catch (error) { - console.error(error); - } + return await tracer.startActiveSpan(`${job.queueName} - ${job.data.type}`, async (span) => { + span.setAttributes({ + 'job.id': job.id, + 'job.name': job.name, + 'job.queueName': job.queueName, + 'job.type': job.data.type, + 'job.opts': JSON.stringify(job.opts), + 'job.data': JSON.stringify(job.data) + }); + try { + job.updateProgress(0); + return await this.run(job); + } catch (error) { + span.recordException(error as Exception); + span.setStatus({ + code: SpanStatusCode.ERROR, // Error + message: (error as Error).message + }); + console.error(error); + throw error; + } finally { + span.end(); + } + }); } abstract run(job: Job): Promise; } diff --git a/src/lib/server/bullmq/queues.ts b/src/lib/server/bullmq/queues.ts index 387e4130..cf17e9cd 100644 --- a/src/lib/server/bullmq/queues.ts +++ b/src/lib/server/bullmq/queues.ts @@ -1,4 +1,5 @@ import { Queue } from 'bullmq'; +import { BullMQOtel } from 'bullmq-otel'; import { Redis } from 'ioredis'; import type { BuildJob, @@ -11,6 +12,7 @@ import type { } from './types'; import { QueueName } from './types'; import { env } from '$env/dynamic/private'; +import OTEL from '$lib/otel'; class Connection { private conn: Redis; @@ -23,12 +25,22 @@ class Connection { }); this.connected = false; this.conn.on('close', () => { + OTEL.instance.logger.info('Valkey connection closed', { + isQueueConnection + }); this.connected = false; }); this.conn.on('connect', () => { + OTEL.instance.logger.info('Valkey connection established', { + isQueueConnection + }); this.connected = true; }); this.conn.on('error', (err) => { + OTEL.instance.logger.error('Valkey connection error', { + error: err.message, + isQueueConnection + }); this.connected = false; if (err.message.includes('ENOTFOUND')) { console.error('Fatal Valkey connection', err); @@ -49,6 +61,10 @@ class Connection { console.error(err); console.log('Valkey disconnected'); this.connected = false; + OTEL.instance.logger.error('Valkey disconnected', { + error: err.message, + isQueueConnection + }); } }); } @@ -86,7 +102,21 @@ export const getQueueConfig = () => { if (!_queueConnection) _queues = createQueues(); return { connection: _queueConnection!.connection(), - prefix: env.APP_ENV + '_build-engine' + prefix: env.APP_ENV + '_build-engine', + telemetry: new BullMQOtel(env.APP_ENV + '_build-engine'), + defaultJobOptions: { + // https://docs.bullmq.io/guide/queues/auto-removal-of-jobs#keep-a-certain-number-of-jobs + removeOnComplete: { + // 2 weeks + age: 2 * 7 * 24 * 60 * 60, + count: 1000 + }, + removeOnFail: { + // 2 weeks + age: 2 * 7 * 24 * 60 * 60, + count: 2000 + } + } } as const; }; let _queues: ReturnType | undefined = undefined; diff --git a/src/lib/server/prisma.ts b/src/lib/server/prisma.ts index a9d03eab..00480064 100644 --- a/src/lib/server/prisma.ts +++ b/src/lib/server/prisma.ts @@ -1,4 +1,5 @@ import { Prisma, PrismaClient } from '@prisma/client'; +import OTEL from '$lib/otel'; export const prisma = new PrismaClient(); @@ -14,6 +15,7 @@ class ConnectionChecker { await prisma.$queryRaw`SELECT 1`; if (!this.connected) { this.connected = true; + OTEL.instance.logger.info('Database connection established'); } } catch (e) { if ( @@ -24,6 +26,9 @@ class ConnectionChecker { // As best as I can tell, the only types of PrismaClientKnownRequestError that // should be thrown by the above query would involve the database being unreachable. if (this.connected) { + OTEL.instance.logger.error('Database connection lost', { + error: e.message + }); this.connected = false; console.log('Error checking database connection:', e); } diff --git a/src/routes/(auth)/exchange/+server.ts b/src/routes/(auth)/exchange/+server.ts index db3735d9..88664e83 100644 --- a/src/routes/(auth)/exchange/+server.ts +++ b/src/routes/(auth)/exchange/+server.ts @@ -1,3 +1,4 @@ +import { trace } from '@opentelemetry/api'; import { error } from 'console'; import { EncryptJWT, jwtVerify } from 'jose'; import type { RequestHandler } from './$types'; @@ -16,6 +17,8 @@ export const GET: RequestHandler = async (event) => { throw error(400, 'Missing URL Search Params'); } + trace.getActiveSpan()?.setAttribute('request-id', requestId); + const verify = await getAuthConnection().get(requestId); if (!verify) { throw error(400, 'Invalid or expired code'); @@ -48,6 +51,8 @@ export const GET: RequestHandler = async (event) => { const token = await jwtVerify(res.id_token, key); + trace.getActiveSpan()?.setAttribute('user.email', token.payload.email as string); + const encryptedToken = await new EncryptJWT(token.payload) .setProtectedHeader({ alg: 'dir', enc: 'A256CBC-HS512' }) .encrypt(key); diff --git a/src/routes/(ui)/build-admin/update/+page.server.ts b/src/routes/(ui)/build-admin/update/+page.server.ts index acb794f0..8d83ea62 100644 --- a/src/routes/(ui)/build-admin/update/+page.server.ts +++ b/src/routes/(ui)/build-admin/update/+page.server.ts @@ -1,3 +1,4 @@ +import { SpanStatusCode, trace } from '@opentelemetry/api'; import { error, redirect } from '@sveltejs/kit'; import { fail, superValidate } from 'sveltekit-superforms'; import { valibot } from 'sveltekit-superforms/adapters'; @@ -72,8 +73,12 @@ export const actions: Actions = { data: form.data }); } catch (e) { - console.log(e); - return error(400, e as Error); + trace.getActiveSpan()?.recordException(e as Error); + trace.getActiveSpan()?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + return error(400, (e as Error).message); } redirect(303, `/build-admin/view?id=${build.id}`); diff --git a/src/routes/(ui)/job-admin/update/+page.server.ts b/src/routes/(ui)/job-admin/update/+page.server.ts index 43584cdd..f27fbaa0 100644 --- a/src/routes/(ui)/job-admin/update/+page.server.ts +++ b/src/routes/(ui)/job-admin/update/+page.server.ts @@ -1,3 +1,4 @@ +import { SpanStatusCode, trace } from '@opentelemetry/api'; import { error, redirect } from '@sveltejs/kit'; import { fail, superValidate } from 'sveltekit-superforms'; import { valibot } from 'sveltekit-superforms/adapters'; @@ -69,8 +70,12 @@ export const actions: Actions = { data: form.data }); } catch (e) { - console.log(e); - return error(400, e as Error); + trace.getActiveSpan()?.recordException(e as Error); + trace.getActiveSpan()?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + return error(400, (e as Error).message); } redirect(303, `/job-admin/view?id=${job.id}`); diff --git a/src/routes/(ui)/project-admin/update/+page.server.ts b/src/routes/(ui)/project-admin/update/+page.server.ts index 2eb5ea54..8d80c60d 100644 --- a/src/routes/(ui)/project-admin/update/+page.server.ts +++ b/src/routes/(ui)/project-admin/update/+page.server.ts @@ -1,3 +1,4 @@ +import { SpanStatusCode, trace } from '@opentelemetry/api'; import { error, redirect } from '@sveltejs/kit'; import { fail, superValidate } from 'sveltekit-superforms'; import { valibot } from 'sveltekit-superforms/adapters'; @@ -68,8 +69,12 @@ export const actions: Actions = { data: form.data }); } catch (e) { - console.log(e); - return error(400, e as Error); + trace.getActiveSpan()?.recordException(e as Error); + trace.getActiveSpan()?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + return error(400, (e as Error).message); } redirect(303, `/project-admin/view?id=${project.id}`); diff --git a/src/routes/(ui)/release-admin/update/+page.server.ts b/src/routes/(ui)/release-admin/update/+page.server.ts index 4b81cd81..b1197764 100644 --- a/src/routes/(ui)/release-admin/update/+page.server.ts +++ b/src/routes/(ui)/release-admin/update/+page.server.ts @@ -1,3 +1,4 @@ +import { SpanStatusCode, trace } from '@opentelemetry/api'; import { error, redirect } from '@sveltejs/kit'; import { fail, superValidate } from 'sveltekit-superforms'; import { valibot } from 'sveltekit-superforms/adapters'; @@ -74,8 +75,12 @@ export const actions: Actions = { data: form.data }); } catch (e) { - console.log(e); - return error(400, e as Error); + trace.getActiveSpan()?.recordException(e as Error); + trace.getActiveSpan()?.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + return error(400, (e as Error).message); } redirect(303, `/release-admin/view?id=${release.id}`); From 8378fc4fa71f1d5883a690d97efe913448691c07 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 25 Feb 2026 14:04:40 -0600 Subject: [PATCH 122/144] Various fixes --- .gitignore | 2 +- src/hooks.server.ts | 2 +- src/routes/(api)/system/check/+server.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 89c72b2f..dca0a413 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ aws.env .netlify .wrangler /.svelte-kit -/build +/out/build # IDE project files .idea/ diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 24e504af..3e1d53bd 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -95,7 +95,7 @@ export const handle: Handle = async ({ event, resolve }) => { const response = await sequence( heartbeat, // Handle auth hooks in a separate OTEL span - (h) => { + async ({ event, resolve }) => { return tracer.startActiveSpan('Authentication', async (span) => { // Call the auth sequence let spanEnded = false; diff --git a/src/routes/(api)/system/check/+server.ts b/src/routes/(api)/system/check/+server.ts index 3721011f..2856ce68 100644 --- a/src/routes/(api)/system/check/+server.ts +++ b/src/routes/(api)/system/check/+server.ts @@ -8,9 +8,9 @@ export const GET: RequestHandler = async () => { return new Response( JSON.stringify({ versions: Object.fromEntries(versions.map(({ appName, version }) => [appName, version])), - created: versions[0].created, - updated: versions[0].updated, - imageHash: versions[0].imageHash, + created: versions.at(0)?.created, + updated: versions.at(0)?.updated, + imageHash: versions.at(0)?.imageHash, _links: { self: { href: `${process.env.ORIGIN || 'http://localhost:8443'}/system/check` From 39e8f6e4fd81274744f7773a844883d7b8330fa8 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Wed, 25 Feb 2026 14:04:31 -0600 Subject: [PATCH 123/144] Cancel build/release on delete --- README.md | 3 +- src/lib/server/aws/codebuild.ts | 25 ++++++++++- src/lib/server/bullmq/BullWorker.ts | 4 ++ src/lib/server/bullmq/types.ts | 21 +++++++++- src/lib/server/job-executors/build.ts | 26 +++++++++++- src/lib/server/job-executors/polling.ts | 4 +- src/lib/server/job-executors/release.ts | 24 ++++++++++- .../build/[buildId=idNumber]/+server.ts | 33 +++++++++++++-- .../release/[releaseId=idNumber]/+server.ts | 42 ++++++++++++++++--- 9 files changed, 164 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 326fe810..82e03d98 100644 --- a/README.md +++ b/README.md @@ -354,7 +354,8 @@ Create the following policies: "codebuild:CreateProject", "codebuild:BatchGetProjects", "codebuild:BatchGetBuilds", - "codebuild:StartBuild" + "codebuild:StartBuild", + "codebuild:StopBuild" ], "Resource": "" }, diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index a8693dab..11cd722c 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -6,7 +6,8 @@ import { CreateProjectCommand, type ProjectCache, type ProjectSource, - StartBuildCommand + StartBuildCommand, + StopBuildCommand } from '@aws-sdk/client-codebuild'; import { SpanStatusCode, trace } from '@opentelemetry/api'; import type { Prisma } from '@prisma/client'; @@ -228,6 +229,28 @@ export class CodeBuild extends AWSCommon { }); } + public async cancelBuild(guid: string, buildProcess: string) { + return tracer.startActiveSpan(`CodeBuild - CancelBuild`, async (span) => { + try { + const existing = await this.getBuildStatus(guid, buildProcess); + if (existing?.id) { + span.setAttribute('code-build.build-status', this.getStatus(existing) ?? '(missing)'); + await this.codeBuildClient.send(new StopBuildCommand({ id: existing.id })); + } + + return existing; + } catch (e) { + span.recordException(e as Error); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: (e as Error).message + }); + } finally { + span.end(); + } + }); + } + /** * This method returns the build status object * diff --git a/src/lib/server/bullmq/BullWorker.ts b/src/lib/server/bullmq/BullWorker.ts index 2a2a898a..5740d17a 100644 --- a/src/lib/server/bullmq/BullWorker.ts +++ b/src/lib/server/bullmq/BullWorker.ts @@ -116,6 +116,8 @@ export class Builds extends BullWorker { switch (job.data.type) { case BullMQ.JobType.Build_Product: return Executor.Build.product(job as Job); + case BullMQ.JobType.Build_Cancel: + return Executor.Build.cancel(job as Job); } } } @@ -154,6 +156,8 @@ export class Releases extends BullWorker { switch (job.data.type) { case BullMQ.JobType.Release_Product: return Executor.Release.product(job as Job); + case BullMQ.JobType.Release_Cancel: + return Executor.Release.cancel(job as Job); } } } diff --git a/src/lib/server/bullmq/types.ts b/src/lib/server/bullmq/types.ts index 2bbc14a7..82c6809c 100644 --- a/src/lib/server/bullmq/types.ts +++ b/src/lib/server/bullmq/types.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ import type { RepeatOptions } from 'bullmq'; +import type { BuildForPrefix, ReleaseForPrefix } from '../models/artifacts'; /** Retry a job for 72 hours every 10 minutes. Useful for build engine tasks */ export const Retry0f600 = { @@ -28,6 +29,7 @@ export enum QueueName { export enum JobType { // Build Jobs Build_Product = 'Build Product', + Build_Cancel = 'Cancel Build', // Polling Jobs Poll_Build = 'Check Product Build', Poll_Release = 'Check Product Release', @@ -35,6 +37,7 @@ export enum JobType { Project_Create = 'Create Project', // Publishing Jobs Release_Product = 'Release Product', + Release_Cancel = 'Cancel Release', // S3 Jobs S3_CopyArtifacts = 'Copy Artifacts to S3', S3_CopyError = 'Copy Errors to S3', @@ -52,6 +55,12 @@ export namespace Build { type: JobType.Build_Product; buildId: number; } + + export interface Cancel { + type: JobType.Build_Cancel; + guid: string; + build: BuildForPrefix; + } } export namespace Polling { @@ -78,6 +87,12 @@ export namespace Release { type: JobType.Release_Product; releaseId: number; } + + export interface Cancel { + type: JobType.Release_Cancel; + guid: string; + release: ReleaseForPrefix; + } } export namespace S3 { @@ -104,9 +119,9 @@ export namespace System { export type Job = JobTypeMap[keyof JobTypeMap]; -export type BuildJob = JobTypeMap[JobType.Build_Product]; +export type BuildJob = JobTypeMap[JobType.Build_Product | JobType.Build_Cancel]; export type S3Job = JobTypeMap[JobType.S3_CopyArtifacts | JobType.S3_CopyError]; -export type PublishJob = JobTypeMap[JobType.Release_Product]; +export type PublishJob = JobTypeMap[JobType.Release_Product | JobType.Release_Cancel]; export type PollJob = JobTypeMap[JobType.Poll_Build | JobType.Poll_Release]; export type ProjectJob = JobTypeMap[JobType.Project_Create]; export type StartupJob = JobTypeMap[ @@ -116,10 +131,12 @@ export type RecurringJob = JobTypeMap[JobType.System_RefreshAppVersions]; export type JobTypeMap = { [JobType.Build_Product]: Build.Product; + [JobType.Build_Cancel]: Build.Cancel; [JobType.Poll_Build]: Polling.Build; [JobType.Poll_Release]: Polling.Release; [JobType.Project_Create]: Project.Create; [JobType.Release_Product]: Release.Product; + [JobType.Release_Cancel]: Release.Cancel; [JobType.S3_CopyArtifacts]: S3.CopyArtifacts; [JobType.S3_CopyError]: S3.CopyErrors; [JobType.System_CreateCodeBuildProject]: System.CreateCodeBuildProject; diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index 193492f5..b6efa5ce 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -4,6 +4,7 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { CodeBuild } from '../aws/codebuild'; import { CodeCommit } from '../aws/codecommit'; +import { S3 } from '../aws/s3'; import { BullMQ, getQueues } from '../bullmq'; import { Build } from '../models/build'; import { prisma } from '../prisma'; @@ -78,7 +79,7 @@ export async function product(job: Job): Promise ) }); } - const name = `Check status of Build #${build.id}`; + const name = pollName(build.id); await getQueues().Polling.upsertJobScheduler(name, BullMQ.RepeatEveryMinute, { name, data: { @@ -127,7 +128,7 @@ export async function product(job: Job): Promise ) }); } - const name = `Check status of Build #${build.id}`; + const name = pollName(build.id); await getQueues().Polling.upsertJobScheduler(name, BullMQ.RepeatEveryMinute, { name, data: { @@ -158,6 +159,23 @@ export async function product(job: Job): Promise } } +export async function cancel(job: Job): Promise { + const pollRemoved = await getQueues().Polling.removeJobScheduler(pollName(job.data.build.id)); + job.updateProgress(10); + const codeBuild = new CodeBuild(); + job.updateProgress(20); + const build = await codeBuild.cancelBuild( + job.data.guid, + CodeBuild.getCodeBuildProjectName('build_app') + ); + job.updateProgress(50); + const s3 = new S3(); + job.updateProgress(60); + const objects = await s3.removeCodeBuildFolder(job.data.build); + job.updateProgress(100); + return { pollRemoved, build, objects }; +} + async function getVersionCode( job: Prisma.jobGetPayload<{ select: { id: true; existing_version_code: true } }> ) { @@ -173,3 +191,7 @@ async function getVersionCode( }); return build._max.version_code ?? job.existing_version_code ?? 0; } + +function pollName(id: number) { + return `Check status of Build #${id}`; +} diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index 3a0e2421..a16f4793 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -71,7 +71,7 @@ export async function build(job: Job): Promise { } } catch (e) { job.log(`${e}`); - await prisma.build.update({ + await prisma.build.updateMany({ where: { id: job.data.buildId }, data: trimStrings( { @@ -162,7 +162,7 @@ export async function release(job: Job): Promise): Promise): Promise): Promise { + const pollRemoved = await getQueues().Polling.removeJobScheduler(pollName(job.data.release.id)); + job.updateProgress(10); + const codeBuild = new CodeBuild(); + job.updateProgress(20); + const release = await codeBuild.cancelBuild( + job.data.guid, + CodeBuild.getCodeBuildProjectName('publish_app') + ); + job.updateProgress(50); + const s3 = new S3(); + job.updateProgress(60); + const objects = await s3.removeCodeBuildFolder(job.data.release); + job.updateProgress(100); + return { pollRemoved, release, objects }; +} + +function pollName(id: number) { + return `Check status of Release #${id}`; +} diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts index 8d6b8ae4..2535ab6e 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/+server.ts @@ -131,8 +131,35 @@ export const PUT: RequestHandler = async ({ request, params }) => { }; // DELETE /job/[id]/build/[id] -export const DELETE: RequestHandler = async () => { - return ErrorResponse(405, 'DELETE /job/[id]/build/[id] is not supported at this time', { - Allow: 'GET' +export const DELETE: RequestHandler = async ({ params }) => { + const build = await prisma.build.findUnique({ + where: { + id: Number(params.buildId) + }, + select: { + id: true, + status: true, + build_guid: true, + job: { + select: { + id: true, + app_id: true + } + } + } }); + + if (!build) return ErrorResponse(404, 'Build not found'); + + await prisma.build.delete({ where: { id: build.id } }); + + if (build.build_guid && build.status !== Build.Status.Completed) { + await getQueues().Builds.add(`Cancel Build #${build.id}`, { + type: BullMQ.JobType.Build_Cancel, + guid: build.build_guid, + build + }); + } + + return new Response(JSON.stringify({})); }; diff --git a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts index f4a107ff..c16a4932 100644 --- a/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts +++ b/src/routes/(api)/job/[jobId=idNumber]/build/[buildId=idNumber]/release/[releaseId=idNumber]/+server.ts @@ -1,4 +1,5 @@ import type { RequestHandler } from './$types'; +import { BullMQ, getQueues } from '$lib/server/bullmq'; import { Release } from '$lib/server/models/release'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; @@ -54,10 +55,39 @@ export const GET: RequestHandler = async ({ params }) => { }; // DELETE /job/[id]/build/[id]/release/[id] -export const DELETE: RequestHandler = async () => { - return ErrorResponse( - 405, - 'DELETE /job/[id]/build/[id]/release/[id] is not supported at this time', - { Allow: 'GET' } - ); +export const DELETE: RequestHandler = async ({ params }) => { + const release = await prisma.release.findUnique({ + where: { + id: Number(params.releaseId) + }, + select: { + id: true, + status: true, + build_guid: true, + build: { + select: { + job: { + select: { + id: true, + app_id: true + } + } + } + } + } + }); + + if (!release) return ErrorResponse(404, 'Release not found'); + + await prisma.build.delete({ where: { id: release.id } }); + + if (release.build_guid && release.status !== Release.Status.Completed) { + await getQueues().Releases.add(`Cancel Release #${release.id}`, { + type: BullMQ.JobType.Release_Cancel, + guid: release.build_guid, + release + }); + } + + return new Response(JSON.stringify({})); }; From a57c2892a9c32311309a367f34ee41da36a1a17b Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 26 Feb 2026 13:31:08 -0600 Subject: [PATCH 124/144] Add comments for #77 --- src/lib/prisma/schema.prisma | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/prisma/schema.prisma b/src/lib/prisma/schema.prisma index 845ea486..948b3e0c 100644 --- a/src/lib/prisma/schema.prisma +++ b/src/lib/prisma/schema.prisma @@ -15,7 +15,7 @@ model build { error String? @db.VarChar(2083) created DateTime? @default(now()) @db.Timestamp(6) updated DateTime? @updatedAt @db.Timestamp(6) - channel String? @db.VarChar(255) + channel String? @db.VarChar(255) // ISSUE #77: remove this? version_code Int? artifact_url_base String? @db.VarChar(2083) artifact_files String? @db.VarChar(4096) @@ -52,8 +52,8 @@ model job { updated DateTime? @updatedAt @db.Timestamp(6) client_id Int? existing_version_code Int? @default(0) - jenkins_build_url String? @db.VarChar(1024) - jenkins_publish_url String? @db.VarChar(1024) + jenkins_build_url String? @db.VarChar(1024) // ISSUE #77: remove this? + jenkins_publish_url String? @db.VarChar(1024) // ISSUE #77: remove this? build build[] client client? @relation(fields: [client_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_job_client_id") @@ -67,12 +67,12 @@ model project { result String? @db.VarChar(255) error String? @db.VarChar(2083) url String? @db.VarChar(1024) - user_id String? @db.VarChar(255) - group_id String? @db.VarChar(255) + user_id String? @db.VarChar(255) // ISSUE #77: remove this? + group_id String? @db.VarChar(255) // ISSUE #77: remove this? app_id String? @db.VarChar(255) project_name String? @db.VarChar(255) language_code String? @db.VarChar(255) - publishing_key String? @db.VarChar(1024) + publishing_key String? @db.VarChar(1024) // ISSUE #77: remove this? created DateTime? @default(now()) @db.Timestamp(6) updated DateTime? @updatedAt @db.Timestamp(6) client_id Int? @@ -90,7 +90,7 @@ model release { result String? @db.VarChar(255) error String? @db.VarChar(2083) channel String @db.VarChar(255) - title String? @db.VarChar(30) + title String? @db.VarChar(30) // ISSUE #77: remove this? defaultLanguage String? @db.VarChar(255) promote_from String? @db.VarChar(255) build_guid String? @db.VarChar(255) From 309fb3a85030921c7e9e133b90233810c68d706a Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 26 Feb 2026 13:31:22 -0600 Subject: [PATCH 125/144] Remove unneeded action --- src/lib/server/actions/project.ts | 84 ------------------------------- 1 file changed, 84 deletions(-) delete mode 100644 src/lib/server/actions/project.ts diff --git a/src/lib/server/actions/project.ts b/src/lib/server/actions/project.ts deleted file mode 100644 index b2d1c69a..00000000 --- a/src/lib/server/actions/project.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { Prisma } from '@prisma/client'; -import { IAmWrapper } from '../aws/iamwrapper'; -import { prisma } from '../prisma'; -import { Utils } from '../utils'; -import { Project } from '$lib/server/models/project'; -import { trimStrings } from '$lib/valibot'; - -export class ProjectUpdateOperation { - private id; - private parms; - public iAmWrapper; // Public for ut - - public constructor(id: number, parms: string) { - this.id = id; - this.parms = parms; - this.iAmWrapper = new IAmWrapper(); - } - public async performOperation() { - console.log(`[${Utils.getPrefix()}] ProjectUpdateOperation ID: ${this.id}`); - const project = await prisma.project.findUnique({ - where: { id: this.id }, - include: { client: true } - }); - if (project) { - console.log('Found record'); - const parmsArray = this.parms.split(','); - const publishing_key = parmsArray[0]; - const user_id = parmsArray[1]; - this.checkRemoveUserFromGroup(project); - this.updateProject(project, user_id, publishing_key); - } else { - console.log("Didn't find record"); - } - } - /** - * If the user/group combination associated with the current project is - * the only project that exists, then remove the IAM user from the IAM group - * - * @param Project project - * @return void - */ - private async checkRemoveUserFromGroup( - project: Prisma.projectGetPayload<{ select: { user_id: true } }> & Project.ProjectGroup - ) { - console.log('checkRemoveUserFromGroup'); - const projects = await prisma.project.count({ - where: { user_id: project.user_id, group_id: project.group_id } - }); - if (projects < 2) { - // Remove the user from the group - console.log( - `CheckRemoveUserFromGroup: Removing [${project.user_id}] from group [${Project.groupName(project)}]` - ); - this.iAmWrapper.removeUserFromIamGroup(project.user_id!, Project.groupName(project)); - } - } - private async updateProject( - project: Prisma.projectGetPayload<{ select: { id: true; url: true } }> & Project.ProjectGroup, - user_id: string, - publishing_key: string - ) { - console.log('updateProject'); - await this.iAmWrapper.createAwsAccount(user_id); - this.iAmWrapper.addUserToIamGroup(user_id, Project.groupName(project)); - const public_key = await this.iAmWrapper.addPublicSshKey(user_id, publishing_key); - const publicKeyId = public_key?.SSHPublicKey?.SSHPublicKeyId; - const url = this.adjustUrl(project.url!, publicKeyId!); - await prisma.project.update({ - where: { id: project.id }, - data: trimStrings( - { - user_id, - publishing_key, - url - }, - 'project' - ) - }); - } - private adjustUrl(url: string, newPublicKeyId: string) { - const oldPublicKeyId = url.split('@')[0]; - return url.replace(oldPublicKeyId, 'ssh://' + newPublicKeyId); - } -} From a5516150449210b4af3e53da7092d4c974f90576 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 26 Feb 2026 14:30:39 -0600 Subject: [PATCH 126/144] Remove unneeded create project job --- src/lib/server/bullmq/BullMQ.ts | 1 - src/lib/server/bullmq/BullWorker.ts | 12 ------------ src/lib/server/bullmq/queues.ts | 13 +------------ src/lib/server/bullmq/types.ts | 12 ------------ src/lib/server/job-executors/index.ts | 1 - src/lib/server/job-executors/project.ts | 6 ------ src/routes/(api)/project/+server.ts | 4 ++-- 7 files changed, 3 insertions(+), 46 deletions(-) delete mode 100644 src/lib/server/job-executors/project.ts diff --git a/src/lib/server/bullmq/BullMQ.ts b/src/lib/server/bullmq/BullMQ.ts index a7f50d3b..a4b28a20 100644 --- a/src/lib/server/bullmq/BullMQ.ts +++ b/src/lib/server/bullmq/BullMQ.ts @@ -6,7 +6,6 @@ export const allWorkers = building : [ new Workers.Builds(), new Workers.S3(), - new Workers.Projects(), new Workers.Releases(), new Workers.Polling(), new Workers.SystemStartup() diff --git a/src/lib/server/bullmq/BullWorker.ts b/src/lib/server/bullmq/BullWorker.ts index 5740d17a..799192d8 100644 --- a/src/lib/server/bullmq/BullWorker.ts +++ b/src/lib/server/bullmq/BullWorker.ts @@ -136,18 +136,6 @@ export class S3 extends BullWorker { } } -export class Projects extends BullWorker { - constructor() { - super(BullMQ.QueueName.Projects); - } - async run(job: Job) { - switch (job.data.type) { - case BullMQ.JobType.Project_Create: - return Executor.Project.create(job as Job); - } - } -} - export class Releases extends BullWorker { constructor() { super(BullMQ.QueueName.Releases); diff --git a/src/lib/server/bullmq/queues.ts b/src/lib/server/bullmq/queues.ts index cf17e9cd..a1c675a0 100644 --- a/src/lib/server/bullmq/queues.ts +++ b/src/lib/server/bullmq/queues.ts @@ -1,15 +1,7 @@ import { Queue } from 'bullmq'; import { BullMQOtel } from 'bullmq-otel'; import { Redis } from 'ioredis'; -import type { - BuildJob, - PollJob, - ProjectJob, - PublishJob, - RecurringJob, - S3Job, - StartupJob -} from './types'; +import type { BuildJob, PollJob, PublishJob, RecurringJob, S3Job, StartupJob } from './types'; import { QueueName } from './types'; import { env } from '$env/dynamic/private'; import OTEL from '$lib/otel'; @@ -129,8 +121,6 @@ function createQueues() { const Builds = new Queue(QueueName.Builds, getQueueConfig()); /** Queue for S3 jobs */ const S3 = new Queue(QueueName.S3, getQueueConfig()); - /** Queue for miscellaneous jobs in BuildEngine such as Product and Project Creation */ - const Projects = new Queue(QueueName.Projects, getQueueConfig()); /** Queue for Product Publishing */ const Releases = new Queue(QueueName.Releases, getQueueConfig()); /** Queue for jobs that poll BuildEngine, such as checking the status of a build */ @@ -142,7 +132,6 @@ function createQueues() { return { Builds, S3, - Projects, Releases, Polling, SystemStartup, diff --git a/src/lib/server/bullmq/types.ts b/src/lib/server/bullmq/types.ts index 82c6809c..c6cc206e 100644 --- a/src/lib/server/bullmq/types.ts +++ b/src/lib/server/bullmq/types.ts @@ -19,7 +19,6 @@ export const RepeatEveryMinute: RepeatOptions = { export enum QueueName { Builds = 'Builds', S3 = 'S3', - Projects = 'Projects', Releases = 'Releases', Polling = 'Polling', System_Startup = 'System (Startup)', @@ -33,8 +32,6 @@ export enum JobType { // Polling Jobs Poll_Build = 'Check Product Build', Poll_Release = 'Check Product Release', - // Project Jobs - Project_Create = 'Create Project', // Publishing Jobs Release_Product = 'Release Product', Release_Cancel = 'Cancel Release', @@ -75,13 +72,6 @@ export namespace Polling { } } -export namespace Project { - export interface Create { - type: JobType.Project_Create; - projectId: number; - } -} - export namespace Release { export interface Product { type: JobType.Release_Product; @@ -123,7 +113,6 @@ export type BuildJob = JobTypeMap[JobType.Build_Product | JobType.Build_Cancel]; export type S3Job = JobTypeMap[JobType.S3_CopyArtifacts | JobType.S3_CopyError]; export type PublishJob = JobTypeMap[JobType.Release_Product | JobType.Release_Cancel]; export type PollJob = JobTypeMap[JobType.Poll_Build | JobType.Poll_Release]; -export type ProjectJob = JobTypeMap[JobType.Project_Create]; export type StartupJob = JobTypeMap[ | JobType.System_CreateCodeBuildProject | JobType.System_RefreshAppVersions]; @@ -134,7 +123,6 @@ export type JobTypeMap = { [JobType.Build_Cancel]: Build.Cancel; [JobType.Poll_Build]: Polling.Build; [JobType.Poll_Release]: Polling.Release; - [JobType.Project_Create]: Project.Create; [JobType.Release_Product]: Release.Product; [JobType.Release_Cancel]: Release.Cancel; [JobType.S3_CopyArtifacts]: S3.CopyArtifacts; diff --git a/src/lib/server/job-executors/index.ts b/src/lib/server/job-executors/index.ts index d7a7066d..24a06146 100644 --- a/src/lib/server/job-executors/index.ts +++ b/src/lib/server/job-executors/index.ts @@ -1,6 +1,5 @@ export * as Build from './build'; export * as Polling from './polling'; -export * as Project from './project'; export * as Release from './release'; export * as S3 from './s3'; export * as System from './system'; diff --git a/src/lib/server/job-executors/project.ts b/src/lib/server/job-executors/project.ts deleted file mode 100644 index 999a1af4..00000000 --- a/src/lib/server/job-executors/project.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Job } from 'bullmq'; -import type { BullMQ } from '../bullmq'; - -export async function create(job: Job): Promise { - return; -} diff --git a/src/routes/(api)/project/+server.ts b/src/routes/(api)/project/+server.ts index d626bbbf..6e6b98f3 100644 --- a/src/routes/(api)/project/+server.ts +++ b/src/routes/(api)/project/+server.ts @@ -25,7 +25,7 @@ const projectSchema = v.strictObject({ export const POST: RequestHandler = async ({ request, locals }) => { const parsed = v.safeParse(projectSchema, await request.json()); if (!parsed.success) return ErrorResponse(400, JSON.stringify(v.flatten(parsed.issues))); - // TODO enqueue project creation job + const withoutStorage = { ...parsed.output, storage_type: undefined }; const project = await prisma.project.create({ data: { @@ -54,7 +54,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { }) ); }; -// TODO create bucket??? + function getS3Folder( project: Prisma.projectGetPayload<{ select: { From acf29e3de35e33c7f90a1d57162283d6439e641a Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 26 Feb 2026 14:31:19 -0600 Subject: [PATCH 127/144] Clean up AWS code --- src/lib/server/aws/codebuild.ts | 59 +++---- src/lib/server/aws/codecommit.ts | 7 +- src/lib/server/aws/common.ts | 58 ------- src/lib/server/aws/iamwrapper.ts | 201 +----------------------- src/lib/server/aws/s3.ts | 52 +++--- src/lib/server/aws/sts.ts | 14 +- src/lib/server/aws/vars.ts | 58 +++++++ src/lib/server/job-executors/build.ts | 18 +-- src/lib/server/job-executors/polling.ts | 5 +- src/lib/server/job-executors/release.ts | 6 +- src/lib/server/job-executors/system.ts | 16 +- src/lib/server/models/build.ts | 2 - src/routes/(api)/project/+server.ts | 4 +- 13 files changed, 133 insertions(+), 367 deletions(-) delete mode 100644 src/lib/server/aws/common.ts create mode 100644 src/lib/server/aws/vars.ts diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index 11cd722c..4c17aeae 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -11,7 +11,7 @@ import { } from '@aws-sdk/client-codebuild'; import { SpanStatusCode, trace } from '@opentelemetry/api'; import type { Prisma } from '@prisma/client'; -import { AWSCommon } from './common'; +import { AWSVars } from './vars'; import { S3 } from './s3'; import { type BuildForPrefix, @@ -48,19 +48,10 @@ export type BuildForCodeBuild = Prisma.buildGetPayload<{ const tracer = trace.getTracer('CodeBuild'); -export class CodeBuild extends AWSCommon { +export class CodeBuild { public codeBuildClient; public constructor() { - super(); - this.codeBuildClient = CodeBuild.getCodeBuildClient(); - } - - /** - * Configure and get the CodeBuild Client - * @return \Aws\CodeBuild\CodeBuildClient - */ - public static getCodeBuildClient() { - return new CodeBuildClient({ region: AWSCommon.getArtifactsBucketRegion() }); + this.codeBuildClient = new CodeBuildClient({ region: AWSVars.artifactsRegion() }); } /** @@ -93,9 +84,9 @@ export class CodeBuild extends AWSCommon { 'code-build.build-id': build.id, 'code-build.version-code': versionCode }); - const artifacts_bucket = CodeBuild.getArtifactsBucket(); - const secretsBucket = CodeBuild.getSecretsBucket(); - const buildApp = CodeBuild.getCodeBuildProjectName('build_app'); + const artifacts_bucket = AWSVars.artifacts(); + const secretsBucket = AWSVars.secrets(); + const buildApp = AWSVars.projectName('build_app'); const buildPath = this.getBuildPath(job); const artifactPath = getArtifactPath(job, 'codebuild-output'); span.setAttribute('code-build.artifact-path', artifactPath); @@ -182,7 +173,7 @@ export class CodeBuild extends AWSCommon { }, { name: 'SCRIPT_S3', - value: S3.getBuildScriptPath() + value: AWSVars.scriptsPath() } ]; const adjustedEnvironmentArray = this.addEnvironmentToArray( @@ -206,7 +197,7 @@ export class CodeBuild extends AWSCommon { environmentVariablesOverride: adjustedEnvironmentArray, sourceTypeOverride: 'NO_SOURCE', computeTypeOverride: computeType, - imageOverride: CodeBuild.getCodeBuildImageRepo() + ':' + imageTag + imageOverride: AWSVars.imageRepo() + ':' + imageTag }) ); const buildId = result.build?.id; @@ -340,11 +331,11 @@ export class CodeBuild extends AWSCommon { const build = release.build; const job = build.job; const buildPath = this.getBuildPath(job); - const artifacts_bucket = CodeBuild.getArtifactsBucket(); + const artifacts_bucket = AWSVars.artifacts(); const artifactPath = getArtifactPath(job, 'codebuild-output', true); - const secretsBucket = CodeBuild.getSecretsBucket(); - const scriptureEarthKey = CodeBuild.getScriptureEarthKey(); - const publishApp = CodeBuild.getCodeBuildProjectName('publish_app'); + const secretsBucket = AWSVars.secrets(); + const scriptureEarthKey = AWSVars.scriptureEarthKey(); + const publishApp = AWSVars.projectName('publish_app'); const promoteFrom = release.promote_from ?? ''; const sourceLocation = this.getSourceLocation(build); @@ -404,7 +395,7 @@ export class CodeBuild extends AWSCommon { }, { name: 'SCRIPT_S3', - value: S3.getBuildScriptPath() + value: AWSVars.scriptsPath() }, { name: 'SCRIPTURE_EARTH_KEY', @@ -462,7 +453,7 @@ export class CodeBuild extends AWSCommon { select: { id: true; artifact_files: true; job: { select: { id: true; app_id: true } } }; }> ) { - const appEnv = S3.getAppEnv(); + const appEnv = AWSVars.appEnv(); const apkFilename = getArtifactFilename(/\.apk$/, build.artifact_files); const sourceLocation = S3.getS3Arn(build, appEnv, apkFilename); return sourceLocation; @@ -474,8 +465,8 @@ export class CodeBuild extends AWSCommon { * @return string - s3:// url format for s3 artifacts folder */ private getArtifactsLocation(build: BuildForPrefix) { - const artifactsBucket = CodeBuild.getArtifactsBucket(); - const artifactFolder = getBasePrefixUrl(build, CodeBuild.getAppEnv()); + const artifactsBucket = AWSVars.artifacts(); + const artifactFolder = getBasePrefixUrl(build, AWSVars.appEnv()); return `s3://${artifactsBucket}/${artifactFolder}`; } @@ -494,8 +485,8 @@ export class CodeBuild extends AWSCommon { cache: ProjectCache, source: ProjectSource ) { - const project_name = CodeBuild.getCodeBuildProjectName(base_name); - const artifacts_bucket = CodeBuild.getArtifactsBucket(); + const project_name = AWSVars.projectName(base_name); + const artifacts_bucket = AWSVars.artifacts(); return await this.codeBuildClient.send( new CreateProjectCommand({ artifacts: { @@ -511,7 +502,7 @@ export class CodeBuild extends AWSCommon { environment: { // REQUIRED computeType: 'BUILD_GENERAL1_SMALL', // REQUIRED - image: CodeBuild.getCodeBuildImageRepo() + ':' + CodeBuild.getCodeBuildImageTag(), // REQUIRED + image: AWSVars.imageRepo() + ':' + AWSVars.imageTag(), // REQUIRED privilegedMode: false, type: 'LINUX_CONTAINER' // REQUIRED }, @@ -523,15 +514,15 @@ export class CodeBuild extends AWSCommon { } public static getConsoleTextUrl(baseName: string, guid: string) { - const projectName = CodeBuild.getCodeBuildProjectName(baseName); - const region = AWSCommon.getArtifactsBucketRegion() ?? 'us-east-1'; + const projectName = AWSVars.projectName(baseName); + const region = AWSVars.artifactsRegion() ?? 'us-east-1'; const regionUrl = `https://console.aws.amazon.com/cloudwatch/home?region=${region}`; const taskExtension = `#logEvent:group=/aws/codebuild/${projectName};stream=${guid}`; return `${regionUrl}${taskExtension}`; } public static getCodeBuildUrl(baseName: string, guid: string) { - const projectName = CodeBuild.getCodeBuildProjectName(baseName); - const region = AWSCommon.getArtifactsBucketRegion() ?? 'us-east-1'; + const projectName = AWSVars.projectName(baseName); + const region = AWSVars.artifactsRegion() ?? 'us-east-1'; const regionUrl = `https://console.aws.amazon.com/codebuild/home?region=${region}`; const taskExtension = `#/builds/${projectName}:${guid}/view/new`; return `${regionUrl}${taskExtension}`; @@ -545,7 +536,7 @@ export class CodeBuild extends AWSCommon { public async projectExists(baseName: string) { let exists = false; try { - const projectName = CodeBuild.getCodeBuildProjectName(baseName); + const projectName = AWSVars.projectName(baseName); trace?.getActiveSpan()?.setAttribute('code-build.project-name', projectName); const result = await this.codeBuildClient.send( new BatchGetProjectsCommand({ @@ -590,7 +581,7 @@ export class CodeBuild extends AWSCommon { private getImageTag(environmentVariables: { name: string; value: string }[]) { return ( environmentVariables.find(({ name }) => name === 'BUILD_IMAGE_TAG')?.value ?? - CodeBuild.getCodeBuildImageTag() + AWSVars.imageTag() ); } diff --git a/src/lib/server/aws/codecommit.ts b/src/lib/server/aws/codecommit.ts index 1d44a640..f3ed3d52 100644 --- a/src/lib/server/aws/codecommit.ts +++ b/src/lib/server/aws/codecommit.ts @@ -4,13 +4,12 @@ import { GetRepositoryCommand } from '@aws-sdk/client-codecommit'; import { SpanStatusCode, trace } from '@opentelemetry/api'; -import { AWSCommon } from './common'; +import { AWSVars } from './vars'; -export class CodeCommit extends AWSCommon { +export class CodeCommit { public codeCommitClient; public constructor() { - super(); this.codeCommitClient = CodeCommit.getCodeCommitClient(); } /** @@ -22,7 +21,7 @@ export class CodeCommit extends AWSCommon { let client: CodeCommitClient | null = null; try { client = new CodeCommitClient({ - region: AWSCommon.getArtifactsBucketRegion() + region: AWSVars.artifactsRegion() }); } catch (e) { span?.recordException(e as Error); diff --git a/src/lib/server/aws/common.ts b/src/lib/server/aws/common.ts deleted file mode 100644 index dd896c20..00000000 --- a/src/lib/server/aws/common.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { env } from '$env/dynamic/private'; - -export class AWSCommon { - public static getArtifactsBucketRegion() { - return env.BUILD_ENGINE_ARTIFACTS_BUCKET_REGION || 'us-east-1'; - } - - public static getAwsRegion() { - return env.AWS_REGION; - } - - public static getArtifactsBucket() { - return env.BUILD_ENGINE_ARTIFACTS_BUCKET; - } - - public static getAWSUserAccount() { - return env.AWS_USER_ID; - } - - public static getAppEnv() { - return env.APP_ENV; - } - - public static getSecretsBucket() { - return env.BUILD_ENGINE_SECRETS_BUCKET; - } - - public static getProjectsBucket() { - return env.BUILD_ENGINE_PROJECTS_BUCKET; - } - - public static getCodeBuildImageTag() { - return env.CODE_BUILD_IMAGE_TAG || 'production'; - } - public static getCodeBuildImageRepo() { - return env.CODE_BUILD_IMAGE_REPO || 'sillsdev/appbuilder-agent'; - } - public static getScriptureEarthKey() { - return env.SCRIPTURE_EARTH_KEY; - } - - public static getBuildScriptPath() { - return `s3://${AWSCommon.getProjectsBucket()}/default`; - } - /** - * Get the project name which is the prd or stg plus build_app or publish_app - * - * @param string baseName build_app or publish_app - * @return string app name - */ - public static getCodeBuildProjectName(baseName: string) { - return `${baseName}-${AWSCommon.getAppEnv()}`; - } - - public static getRoleName(baseName: string) { - return `codebuild-${baseName}-service-role-${AWSCommon.getAppEnv()}`; - } -} diff --git a/src/lib/server/aws/iamwrapper.ts b/src/lib/server/aws/iamwrapper.ts index 8615dde3..aa72e8c1 100644 --- a/src/lib/server/aws/iamwrapper.ts +++ b/src/lib/server/aws/iamwrapper.ts @@ -1,70 +1,13 @@ -import { CodeCommitClient } from '@aws-sdk/client-codecommit'; -import { - AddUserToGroupCommand, - CreateUserCommand, - DuplicateSSHPublicKeyException, - EntityAlreadyExistsException, - GetRoleCommand, - GetSSHPublicKeyCommand, - GetUserCommand, - IAMClient, - ListSSHPublicKeysCommand, - RemoveUserFromGroupCommand, - UploadSSHPublicKeyCommand -} from '@aws-sdk/client-iam'; +import { GetRoleCommand, IAMClient } from '@aws-sdk/client-iam'; import { trace } from '@opentelemetry/api'; -import { AWSCommon } from './common'; -import { env } from '$env/dynamic/private'; +import { AWSVars } from './vars'; -export class IAmWrapper extends AWSCommon { +export class IAmWrapper { public iamClient; public constructor() { - super(); - this.iamClient = this.getIamClientNoCredentials(); - } - getIamClientNoCredentials() { - return new IAMClient({ region: AWSCommon.getAwsRegion() }); - } - - getIamClient() { - return new IAMClient({ - region: AWSCommon.getAwsRegion(), - credentials: { - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - accessKeyId: env.AWS_ACCESS_KEY_ID - } - }); - } - getCodecommitClient() { - return new CodeCommitClient({ - region: AWSCommon.getAwsRegion(), - credentials: { - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - accessKeyId: env.AWS_ACCESS_KEY_ID - } - }); + this.iamClient = new IAMClient({ region: AWSVars.region() }); } - /** - * Determines whether the role for the specified project - * and the current production stage exists - * - * @param string projectName - base project name, i + e + build_app or publish_app - * @return boolean - true if role associated with base project exists for this production stage - */ - public async doesRoleExist(projectName: string) { - try { - const fullRoleName = AWSCommon.getRoleName(projectName); - await this.iamClient.send( - new GetRoleCommand({ - RoleName: fullRoleName // REQUIRED - }) - ); - return true; - } catch { - return false; - } - } /** * This method returns the role arn * @param string projectName - base project name, i + e + build_app or publish_app @@ -72,7 +15,7 @@ export class IAmWrapper extends AWSCommon { */ public async getRoleArn(projectName: string) { try { - const fullRoleName = AWSCommon.getRoleName(projectName); + const fullRoleName = AWSVars.roleName(projectName); const result = await this.iamClient.send( new GetRoleCommand({ RoleName: fullRoleName @@ -85,138 +28,4 @@ export class IAmWrapper extends AWSCommon { return ''; } } - - /** - * gets the iam arn of a specific iam policy - * - * @param string base policy name, e + g + s3-appbuild-secrets - * @return string arn for policy - */ - public static getPolicyArn(basePolicyName: string) { - return `arn:aws:iam.${AWSCommon.getAWSUserAccount()}:policy/${basePolicyName}-${AWSCommon.getAppEnv()}`; - } - /** - * gets the iam user if it exists or creates one if it does not - * - * @param string user_id - Project user id - * @return User User from Iam; - */ - public async createAwsAccount(user_id: string) { - const iamClient = this.getIamClient(); - - try { - return iamClient.send( - new CreateUserCommand({ - Path: '/sab-codecommit-users/', - UserName: user_id - }) - ); - } catch (e) { - if (e instanceof EntityAlreadyExistsException) { - // They already have an account - pass back their account - return await iamClient.send( - new GetUserCommand({ - UserName: user_id - }) - ); - } else { - throw e; - } - } - } - - /** - * adds a user to the specified IAM Group - * - * @param string userName - * @param string groupName - * @return IAM always returns empty array so that is what is being returned - */ - public async addUserToIamGroup(userName: string, groupName: string) { - const iamClient = this.getIamClient(); - - return await iamClient.send( - new AddUserToGroupCommand({ - GroupName: groupName, - UserName: userName - }) - ); - } - /** - * removes a user to the specified IAM Group - * - * @param string userName - * @param string groupName - * @return IAM always returns empty array so that is what is being returned - */ - public async removeUserFromIamGroup(userName: string, groupName: string) { - const iamClient = this.getIamClient(); - - return await iamClient.send( - new RemoveUserFromGroupCommand({ - GroupName: groupName, - UserName: userName - }) - ); - } - - /** - * adds the public key for a user to IAM - * - * @param string username - The name of the user associated with the key - * @param string publicKey - The ssh key for the user - * @return SSHPublicKey - See AWS API documentation - */ - public async addPublicSshKey(username: string, publicKey: string) { - const iamClient = this.getIamClient(); - try { - return await iamClient.send( - new UploadSSHPublicKeyCommand({ - SSHPublicKeyBody: publicKey, - UserName: username - }) - ); - } catch (e) { - if (e instanceof DuplicateSSHPublicKeyException) { - const keysForRequester = await iamClient.send( - new ListSSHPublicKeysCommand({ - UserName: username - }) - ); - - for (const requesterKey of keysForRequester.SSHPublicKeys ?? []) { - const key = await iamClient.send( - new GetSSHPublicKeyCommand({ - UserName: username, - SSHPublicKeyId: requesterKey.SSHPublicKeyId, - Encoding: 'SSH' - }) - ); - - if (this.isEqual(key.SSHPublicKey?.SSHPublicKeyBody ?? ' ', publicKey)) { - return key; - } - } - throw new Error(`'SAB: Unable to find a matching ssh key for user + ${e}`); - } else { - throw new Error(`'SAB: Unable to add ssh key to user + ${e}`); - } - } - } - /** - * used to determine whether two openSSH formatted keys are equal (without regard to comment portion of key) - * @param openSSHKey1 format expected: type data comment - * @param openSSHKey2 format expected: type data comment - * @return bool - */ - private isEqual(openSSHKey1: string, openSSHKey2: string) { - const [type1, data1] = openSSHKey1.split(' '); - const [type2, data2] = openSSHKey2.split(' '); - - if (type1 === type2 && data1 === data2) { - return true; - } - - return false; - } } diff --git a/src/lib/server/aws/s3.ts b/src/lib/server/aws/s3.ts index 48fc53c7..0de03021 100644 --- a/src/lib/server/aws/s3.ts +++ b/src/lib/server/aws/s3.ts @@ -12,7 +12,7 @@ import { import { SpanStatusCode, trace } from '@opentelemetry/api'; import { basename, dirname, extname } from 'node:path'; import { S3SyncClient } from 's3-sync-client'; -import { AWSCommon } from './common'; +import { AWSVars } from './vars'; import { env } from '$env/dynamic/private'; import { type BuildForPrefix, @@ -23,29 +23,11 @@ import { handleArtifact } from '$lib/server/models/artifacts'; -export class S3 extends AWSCommon { +export class S3 { public s3Client; public constructor() { - super(); - this.s3Client = S3.getS3Client(); - } - - /** - * Configure and get the S3 Client - * @return \Aws\S3\S3Client - */ - public static getS3Client() { - return new S3Client({ - region: AWSCommon.getArtifactsBucketRegion() - }); - } - public getS3ClientWithCredentials() { - return new S3Client({ - region: AWSCommon.getArtifactsBucketRegion(), - credentials: { - accessKeyId: env.AWS_ACCESS_KEY_ID, - secretAccessKey: env.AWS_SECRET_ACCESS_KEY - } + this.s3Client = new S3Client({ + region: AWSVars.artifactsRegion() }); } @@ -58,7 +40,7 @@ export class S3 extends AWSCommon { * @return string prefix */ public static getS3Arn(build: BuildForPrefix, productStage: string, filename: string | null) { - return `arn:aws:s3:::${S3.getArtifactsBucket()}/${getBasePrefixUrl(build, productStage)}/${filename ?? ''}`; + return `arn:aws:s3:::${AWSVars.artifacts()}/${getBasePrefixUrl(build, productStage)}/${filename ?? ''}`; } /** * This reads a file from the build output @@ -69,7 +51,7 @@ export class S3 extends AWSCommon { */ public async readS3File(artifacts_provider: ProviderForPrefix, fileName: string) { let fileContents = ''; - const bucket = S3.getArtifactsBucket(); + const bucket = AWSVars.artifacts(); const filePath = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/' + fileName; try { const result = await this.s3Client.send( @@ -115,8 +97,8 @@ export class S3 extends AWSCommon { */ public async copyS3Folder(artifacts_provider: ProviderForPrefix & ProviderForArtifacts) { const span = trace.getActiveSpan(); - const artifactsBucket = S3.getArtifactsBucket(); - const destFolderPrefix = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()); + const artifactsBucket = AWSVars.artifacts(); + const destFolderPrefix = getBasePrefixUrl(artifacts_provider, AWSVars.appEnv()); const sourcePrefix = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; const destPrefix = destFolderPrefix + '/'; beginArtifacts(artifacts_provider, `https://${artifactsBucket}.s3.amazonaws.com/${destPrefix}`); @@ -170,7 +152,7 @@ export class S3 extends AWSCommon { artifacts_provider: ProviderForPrefix & ProviderForArtifacts ) { const span = trace.getActiveSpan(); - const artifactsBucket = S3.getArtifactsBucket(); + const artifactsBucket = AWSVars.artifacts(); let fileContents = ''; const fileNameWithPrefix = file['Key']!; const fileName = fileNameWithPrefix.substring(sourcePrefix.length); @@ -226,8 +208,8 @@ export class S3 extends AWSCommon { fileName: string, artifacts_provider: ProviderForPrefix & ProviderForArtifacts ) { - const fileS3Bucket = S3.getArtifactsBucket(); - const destPrefix: string = getBasePrefixUrl(artifacts_provider, S3.getAppEnv()); + const fileS3Bucket = AWSVars.artifacts(); + const destPrefix: string = getBasePrefixUrl(artifacts_provider, AWSVars.appEnv()); const fileS3Key = destPrefix + '/' + fileName; trace.getActiveSpan()?.addEvent(`S3 - Write File`, { @@ -249,7 +231,7 @@ export class S3 extends AWSCommon { } public async removeCodeBuildFolder(artifacts_provider: ProviderForPrefix) { const s3Folder = getBasePrefixUrl(artifacts_provider, 'codebuild-output') + '/'; - const s3Bucket = S3.getArtifactsBucket(); + const s3Bucket = AWSVars.artifacts(); trace.getActiveSpan()?.addEvent(`S3 - Remove CodeBuild Folder`, { 's3.bucket': s3Bucket, 's3.folder': s3Folder @@ -262,7 +244,15 @@ export class S3 extends AWSCommon { 's3.bucket': bucket, 's3.folder': folderName }); - const client = new S3SyncClient({ client: this.getS3ClientWithCredentials() }); + const client = new S3SyncClient({ + client: new S3Client({ + region: AWSVars.artifactsRegion(), + credentials: { + accessKeyId: env.AWS_ACCESS_KEY_ID, + secretAccessKey: env.AWS_SECRET_ACCESS_KEY + } + }) + }); return await client.sync(folderName, bucket); } diff --git a/src/lib/server/aws/sts.ts b/src/lib/server/aws/sts.ts index 6e9f9b68..f876343d 100644 --- a/src/lib/server/aws/sts.ts +++ b/src/lib/server/aws/sts.ts @@ -1,26 +1,20 @@ import { GetFederationTokenCommand, STSClient } from '@aws-sdk/client-sts'; import type { Prisma } from '@prisma/client'; import { randomBytes } from 'crypto'; -import { AWSCommon } from './common'; +import { AWSVars } from './vars'; -export class STS extends AWSCommon { +export class STS { public stsClient: STSClient; public constructor() { - super(); - this.stsClient = STS.getStsClient(); - } - - public static getStsClient() { - // version was set by PHP code to 2011-06-15 ??? - return new STSClient({ region: AWSCommon.getArtifactsBucketRegion() }); + this.stsClient = new STSClient({ region: AWSVars.artifactsRegion() }); } public async getFederationToken(Name: string, Policy: string, ReadOnly: boolean) { const result = await this.stsClient.send(new GetFederationTokenCommand({ Name, Policy })); return { ...result.Credentials, - Region: AWSCommon.getArtifactsBucketRegion(), + Region: AWSVars.artifactsRegion(), ReadOnly }; } diff --git a/src/lib/server/aws/vars.ts b/src/lib/server/aws/vars.ts new file mode 100644 index 00000000..45f21835 --- /dev/null +++ b/src/lib/server/aws/vars.ts @@ -0,0 +1,58 @@ +import { env } from '$env/dynamic/private'; + +export class AWSVars { + public static artifactsRegion() { + return env.BUILD_ENGINE_ARTIFACTS_BUCKET_REGION || 'us-east-1'; + } + + public static region() { + return env.AWS_REGION; + } + + public static artifacts() { + return env.BUILD_ENGINE_ARTIFACTS_BUCKET; + } + + public static userId() { + return env.AWS_USER_ID; + } + + public static appEnv() { + return env.APP_ENV; + } + + public static secrets() { + return env.BUILD_ENGINE_SECRETS_BUCKET; + } + + public static projects() { + return env.BUILD_ENGINE_PROJECTS_BUCKET; + } + + public static imageTag() { + return env.CODE_BUILD_IMAGE_TAG || 'production'; + } + public static imageRepo() { + return env.CODE_BUILD_IMAGE_REPO || 'sillsdev/appbuilder-agent'; + } + public static scriptureEarthKey() { + return env.SCRIPTURE_EARTH_KEY; + } + + public static scriptsPath() { + return `s3://${AWSVars.projects()}/default`; + } + /** + * Get the project name which is the prd or stg plus build_app or publish_app + * + * @param string baseName build_app or publish_app + * @return string app name + */ + public static projectName(baseName: string) { + return `${baseName}-${AWSVars.appEnv()}`; + } + + public static roleName(baseName: string) { + return `codebuild-${baseName}-service-role-${AWSVars.appEnv()}`; + } +} diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index b6efa5ce..6213b51f 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -5,6 +5,7 @@ import { join } from 'node:path'; import { CodeBuild } from '../aws/codebuild'; import { CodeCommit } from '../aws/codecommit'; import { S3 } from '../aws/s3'; +import { AWSVars } from '../aws/vars'; import { BullMQ, getQueues } from '../bullmq'; import { Build } from '../models/build'; import { prisma } from '../prisma'; @@ -22,18 +23,6 @@ export async function product(job: Job): Promise }); job.updateProgress(10); - // Don't start job if a job for this build is currently running - const builds = await prisma.build.count({ - where: { - job_id: build.job_id, - status: { in: [Build.Status.Active, Build.Status.PostProcessing] } - } - }); - if (builds > 0) { - job.log('Existing active builds found. Build cancelled'); - // TODO retry after??? - return { existing: builds }; - } const gitUrl = build.job.git_url; // Check to see if codebuild project const codeCommitProject = gitUrl.startsWith('ssh://'); @@ -164,10 +153,7 @@ export async function cancel(job: Job): Promise { job.updateProgress(10); const codeBuild = new CodeBuild(); job.updateProgress(20); - const build = await codeBuild.cancelBuild( - job.data.guid, - CodeBuild.getCodeBuildProjectName('build_app') - ); + const build = await codeBuild.cancelBuild(job.data.guid, AWSVars.projectName('build_app')); job.updateProgress(50); const s3 = new S3(); job.updateProgress(60); diff --git a/src/lib/server/job-executors/polling.ts b/src/lib/server/job-executors/polling.ts index a16f4793..1eb4da93 100644 --- a/src/lib/server/job-executors/polling.ts +++ b/src/lib/server/job-executors/polling.ts @@ -1,6 +1,7 @@ import type { Prisma } from '@prisma/client'; import type { Job } from 'bullmq'; import { CodeBuild } from '../aws/codebuild'; +import { AWSVars } from '../aws/vars'; import { BullMQ, getQueues } from '../bullmq'; import { Build } from '../models/build'; import { prisma } from '../prisma'; @@ -19,7 +20,7 @@ export async function build(job: Job): Promise { const codeBuild = new CodeBuild(); const buildStatus = await codeBuild.getBuildStatus( build.build_guid!, - CodeBuild.getCodeBuildProjectName('build_app') + AWSVars.projectName('build_app') ); const phase = buildStatus?.currentPhase; let status = buildStatus?.buildStatus; @@ -109,7 +110,7 @@ export async function release(job: Job): Promise): Promise job.updateProgress(10); const codeBuild = new CodeBuild(); job.updateProgress(20); - const release = await codeBuild.cancelBuild( - job.data.guid, - CodeBuild.getCodeBuildProjectName('publish_app') - ); + const release = await codeBuild.cancelBuild(job.data.guid, AWSVars.projectName('publish_app')); job.updateProgress(50); const s3 = new S3(); job.updateProgress(60); diff --git a/src/lib/server/job-executors/system.ts b/src/lib/server/job-executors/system.ts index 7b96eaac..73a872f6 100644 --- a/src/lib/server/job-executors/system.ts +++ b/src/lib/server/job-executors/system.ts @@ -16,7 +16,7 @@ import { IAmWrapper } from '../aws/iamwrapper'; import { S3 } from '../aws/s3'; import type { BullMQ } from '../bullmq'; import { prisma } from '../prisma'; -import { AWSCommon } from '$lib/server/aws/common'; +import { AWSVars } from '$lib/server/aws/vars'; type Logger = (msg: string) => void; @@ -27,7 +27,7 @@ export async function createCodeBuildProject( const build = await createProject( 'build_app', { - location: S3.getArtifactsBucket() + '/codebuild-cache', + location: AWSVars.artifacts() + '/codebuild-cache', type: 'S3' }, { @@ -45,7 +45,7 @@ export async function createCodeBuildProject( // Copy default file const project = copyFolder( join(process.cwd(), './scripts/project_default'), - 's3://' + S3.getArtifactsBucket(), + 's3://' + AWSVars.artifacts(), (msg) => job.log(msg) ); @@ -59,7 +59,7 @@ export async function createCodeBuildProject( { buildspec: 'version: 0.2', gitCloneDepth: 1, - location: `arn:aws:s3:::${S3.getArtifactsBucket()}/default/default.zip`, + location: `arn:aws:s3:::${AWSVars.artifacts()}/default/default.zip`, type: 'S3' }, (msg) => job.log(msg) @@ -69,7 +69,7 @@ export async function createCodeBuildProject( const scripts = await copyFolder( join(process.cwd(), './scripts/upload'), - 's3://' + S3.getProjectsBucket(), + 's3://' + AWSVars.projects(), (msg) => job.log(msg) ); @@ -135,9 +135,9 @@ export async function refreshAppVersions( let versions: Map | null = null; let imageHash: string | null = null; - const repoConfig = AWSCommon.getCodeBuildImageRepo(); - const tagFilter = AWSCommon.getCodeBuildImageTag(); - const region = AWSCommon.getArtifactsBucketRegion(); + const repoConfig = AWSVars.imageRepo(); + const tagFilter = AWSVars.imageTag(); + const region = AWSVars.artifactsRegion(); // Status: log repo config presence job.log(`repoConfig=${repoConfig ?? '(none)'}`); diff --git a/src/lib/server/models/build.ts b/src/lib/server/models/build.ts index b2052bdb..8b94b57f 100644 --- a/src/lib/server/models/build.ts +++ b/src/lib/server/models/build.ts @@ -115,8 +115,6 @@ export namespace Build { }> ) { const { targets, artifact_url_base: base, artifact_files: files } = build; - // We need to at least have one artifact or the current Portal will fail to parse the JSON. - // TODO: Treat these like the others once Portal is fixed. const artifacts = { [Artifact.Version]: getArtifactUrl(/version\.json/, base, files), [Artifact.CloudWatch]: build.console_text_url, diff --git a/src/routes/(api)/project/+server.ts b/src/routes/(api)/project/+server.ts index 6e6b98f3..3d32f281 100644 --- a/src/routes/(api)/project/+server.ts +++ b/src/routes/(api)/project/+server.ts @@ -1,7 +1,7 @@ import type { Prisma } from '@prisma/client'; import * as v from 'valibot'; import type { RequestHandler } from './$types'; -import { AWSCommon } from '$lib/server/aws/common'; +import { AWSVars } from '$lib/server/aws/vars'; import { prisma } from '$lib/server/prisma'; import { ErrorResponse } from '$lib/utils'; import { stringLimits } from '$lib/valibot'; @@ -38,7 +38,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { client: true } }); - const url = `s3://${AWSCommon.getProjectsBucket()}/${getS3Folder(project)}`; + const url = `s3://${AWSVars.projects()}/${getS3Folder(project)}`; await prisma.project.update({ where: { id: project.id }, data: { url } }); return new Response( JSON.stringify({ From 8982ac0f60c761a921aa35f694184c289d424b59 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 26 Feb 2026 14:48:18 -0600 Subject: [PATCH 128/144] Remove CodeCommit --- package-lock.json | 495 -------------------------- package.json | 1 - src/lib/server/aws/codebuild.ts | 192 ++++------ src/lib/server/aws/codecommit.ts | 136 ------- src/lib/server/job-executors/build.ts | 141 ++------ 5 files changed, 106 insertions(+), 859 deletions(-) delete mode 100644 src/lib/server/aws/codecommit.ts diff --git a/package-lock.json b/package-lock.json index 1d22a78f..e0d5e665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.1", "dependencies": { "@aws-sdk/client-codebuild": "^3.907.0", - "@aws-sdk/client-codecommit": "^3.910.0", "@aws-sdk/client-ecr": "^3.981.0", "@aws-sdk/client-iam": "^3.911.0", "@aws-sdk/client-s3": "^3.907.0", @@ -433,500 +432,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/@aws-sdk/client-codecommit": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-codecommit/-/client-codecommit-3.910.0.tgz", - "integrity": "sha512-1SaLByFqLQmJiCHFkchCcpY/volmJRsqeB0/OrKDjZdAf8ARirr6Kzaaey6vJ/TIBm33/5dwVW/g+xY27VcmkA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.910.0", - "@aws-sdk/credential-provider-node": "3.910.0", - "@aws-sdk/middleware-host-header": "3.910.0", - "@aws-sdk/middleware-logger": "3.910.0", - "@aws-sdk/middleware-recursion-detection": "3.910.0", - "@aws-sdk/middleware-user-agent": "3.910.0", - "@aws-sdk/region-config-resolver": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@aws-sdk/util-endpoints": "3.910.0", - "@aws-sdk/util-user-agent-browser": "3.910.0", - "@aws-sdk/util-user-agent-node": "3.910.0", - "@smithy/config-resolver": "^4.3.2", - "@smithy/core": "^3.16.1", - "@smithy/fetch-http-handler": "^5.3.3", - "@smithy/hash-node": "^4.2.2", - "@smithy/invalid-dependency": "^4.2.2", - "@smithy/middleware-content-length": "^4.2.2", - "@smithy/middleware-endpoint": "^4.3.3", - "@smithy/middleware-retry": "^4.4.3", - "@smithy/middleware-serde": "^4.2.2", - "@smithy/middleware-stack": "^4.2.2", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/node-http-handler": "^4.4.1", - "@smithy/protocol-http": "^5.3.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", - "@smithy/url-parser": "^4.2.2", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.2", - "@smithy/util-defaults-mode-node": "^4.2.3", - "@smithy/util-endpoints": "^3.2.2", - "@smithy/util-middleware": "^4.2.2", - "@smithy/util-retry": "^4.2.2", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/client-sso": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.910.0.tgz", - "integrity": "sha512-oEWXhe2RHiSPKxhrq1qp7M4fxOsxMIJc4d75z8tTLLm5ujlmTZYU3kd0l2uBBaZSlbkrMiefntT6XrGint1ibw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.910.0", - "@aws-sdk/middleware-host-header": "3.910.0", - "@aws-sdk/middleware-logger": "3.910.0", - "@aws-sdk/middleware-recursion-detection": "3.910.0", - "@aws-sdk/middleware-user-agent": "3.910.0", - "@aws-sdk/region-config-resolver": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@aws-sdk/util-endpoints": "3.910.0", - "@aws-sdk/util-user-agent-browser": "3.910.0", - "@aws-sdk/util-user-agent-node": "3.910.0", - "@smithy/config-resolver": "^4.3.2", - "@smithy/core": "^3.16.1", - "@smithy/fetch-http-handler": "^5.3.3", - "@smithy/hash-node": "^4.2.2", - "@smithy/invalid-dependency": "^4.2.2", - "@smithy/middleware-content-length": "^4.2.2", - "@smithy/middleware-endpoint": "^4.3.3", - "@smithy/middleware-retry": "^4.4.3", - "@smithy/middleware-serde": "^4.2.2", - "@smithy/middleware-stack": "^4.2.2", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/node-http-handler": "^4.4.1", - "@smithy/protocol-http": "^5.3.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", - "@smithy/url-parser": "^4.2.2", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.2", - "@smithy/util-defaults-mode-node": "^4.2.3", - "@smithy/util-endpoints": "^3.2.2", - "@smithy/util-middleware": "^4.2.2", - "@smithy/util-retry": "^4.2.2", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/core": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.910.0.tgz", - "integrity": "sha512-b/FVNyPxZMmBp+xDwANDgR6o5Ehh/RTY9U/labH56jJpte196Psru/FmQULX3S6kvIiafQA9JefWUq81SfWVLg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.910.0", - "@aws-sdk/xml-builder": "3.910.0", - "@smithy/core": "^3.16.1", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/property-provider": "^4.2.2", - "@smithy/protocol-http": "^5.3.2", - "@smithy/signature-v4": "^5.3.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.2", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.910.0.tgz", - "integrity": "sha512-Os8I5XtTLBBVyHJLxrEB06gSAZeFMH2jVoKhAaFybjOTiV7wnjBgjvWjRfStnnXs7p9d+vc/gd6wIZHjony5YQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/property-provider": "^4.2.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.910.0.tgz", - "integrity": "sha512-3KiGsTlqMnvthv90K88Uv3SvaUbmcTShBIVWYNaHdbrhrjVRR08dm2Y6XjQILazLf1NPFkxUou1YwCWK4nae1Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/fetch-http-handler": "^5.3.3", - "@smithy/node-http-handler": "^4.4.1", - "@smithy/property-provider": "^4.2.2", - "@smithy/protocol-http": "^5.3.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", - "@smithy/util-stream": "^4.5.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.910.0.tgz", - "integrity": "sha512-/8x9LKKaLGarvF1++bFEFdIvd9/djBb+HTULbJAf4JVg3tUlpHtGe7uquuZaQkQGeW4XPbcpB9RMWx5YlZkw3w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.910.0", - "@aws-sdk/credential-provider-env": "3.910.0", - "@aws-sdk/credential-provider-http": "3.910.0", - "@aws-sdk/credential-provider-process": "3.910.0", - "@aws-sdk/credential-provider-sso": "3.910.0", - "@aws-sdk/credential-provider-web-identity": "3.910.0", - "@aws-sdk/nested-clients": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/credential-provider-imds": "^4.2.2", - "@smithy/property-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.910.0.tgz", - "integrity": "sha512-Zz5tF/U4q9ir3rfVnPLlxbhMTHjPaPv78TarspFYn9mNN7cPVXBaXVVnMNu6ypZzBdTB8M44UYo827Qcw3kouA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.910.0", - "@aws-sdk/credential-provider-http": "3.910.0", - "@aws-sdk/credential-provider-ini": "3.910.0", - "@aws-sdk/credential-provider-process": "3.910.0", - "@aws-sdk/credential-provider-sso": "3.910.0", - "@aws-sdk/credential-provider-web-identity": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/credential-provider-imds": "^4.2.2", - "@smithy/property-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.910.0.tgz", - "integrity": "sha512-l1lZfHIl/z0SxXibt7wMQ2HmRIyIZjlOrT6a554xlO//y671uxPPwScVw7QW4fPIvwfmKbl8dYCwGI//AgQ0bA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/property-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.910.0.tgz", - "integrity": "sha512-cwc9bmomjUqPDF58THUCmEnpAIsCFV3Y9FHlQmQbMkYUm7Wlrb5E2iFrZ4WDefAHuh25R/gtj+Yo74r3gl9kbw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.910.0", - "@aws-sdk/core": "3.910.0", - "@aws-sdk/token-providers": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/property-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.910.0.tgz", - "integrity": "sha512-HFQgZm1+7WisJ8tqcZkNRRmnoFO+So+L12wViVxneVJ+OclfL2vE/CoKqHTozP6+JCOKMlv6Vi61Lu6xDtKdTA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.910.0", - "@aws-sdk/nested-clients": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/property-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.910.0.tgz", - "integrity": "sha512-F9Lqeu80/aTM6S/izZ8RtwSmjfhWjIuxX61LX+/9mxJyEkgaECRxv0chsLQsLHJumkGnXRy/eIyMLBhcTPF5vg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.910.0", - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/middleware-logger": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.910.0.tgz", - "integrity": "sha512-3LJyyfs1USvRuRDla1pGlzGRtXJBXD1zC9F+eE9Iz/V5nkmhyv52A017CvKWmYoR0DM9dzjLyPOI0BSSppEaTw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.910.0", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.910.0.tgz", - "integrity": "sha512-m/oLz0EoCy+WoIVBnXRXJ4AtGpdl0kPE7U+VH9TsuUzHgxY1Re/176Q1HWLBRVlz4gr++lNsgsMWEC+VnAwMpw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.910.0", - "@aws/lambda-invoke-store": "^0.0.1", - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.910.0.tgz", - "integrity": "sha512-djpnECwDLI/4sck1wxK/cZJmZX5pAhRvjONyJqr0AaOfJyuIAG0PHLe7xwCrv2rCAvIBR9ofnNFzPIGTJPDUwg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@aws-sdk/util-endpoints": "3.910.0", - "@smithy/core": "^3.16.1", - "@smithy/protocol-http": "^5.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/nested-clients": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.910.0.tgz", - "integrity": "sha512-Jr/smgVrLZECQgMyP4nbGqgJwzFFbkjOVrU8wh/gbVIZy1+Gu6R7Shai7KHDkEjwkGcHpN1MCCO67jTAOoSlMw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.910.0", - "@aws-sdk/middleware-host-header": "3.910.0", - "@aws-sdk/middleware-logger": "3.910.0", - "@aws-sdk/middleware-recursion-detection": "3.910.0", - "@aws-sdk/middleware-user-agent": "3.910.0", - "@aws-sdk/region-config-resolver": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@aws-sdk/util-endpoints": "3.910.0", - "@aws-sdk/util-user-agent-browser": "3.910.0", - "@aws-sdk/util-user-agent-node": "3.910.0", - "@smithy/config-resolver": "^4.3.2", - "@smithy/core": "^3.16.1", - "@smithy/fetch-http-handler": "^5.3.3", - "@smithy/hash-node": "^4.2.2", - "@smithy/invalid-dependency": "^4.2.2", - "@smithy/middleware-content-length": "^4.2.2", - "@smithy/middleware-endpoint": "^4.3.3", - "@smithy/middleware-retry": "^4.4.3", - "@smithy/middleware-serde": "^4.2.2", - "@smithy/middleware-stack": "^4.2.2", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/node-http-handler": "^4.4.1", - "@smithy/protocol-http": "^5.3.2", - "@smithy/smithy-client": "^4.8.1", - "@smithy/types": "^4.7.1", - "@smithy/url-parser": "^4.2.2", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.2", - "@smithy/util-defaults-mode-node": "^4.2.3", - "@smithy/util-endpoints": "^3.2.2", - "@smithy/util-middleware": "^4.2.2", - "@smithy/util-retry": "^4.2.2", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.910.0.tgz", - "integrity": "sha512-gzQAkuHI3xyG6toYnH/pju+kc190XmvnB7X84vtN57GjgdQJICt9So/BD0U6h+eSfk9VBnafkVrAzBzWMEFZVw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.910.0", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/types": "^4.7.1", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/token-providers": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.910.0.tgz", - "integrity": "sha512-dQr3pFpzemKyrB7SEJ2ipPtWrZiL5vaimg2PkXpwyzGrigYRc8F2R9DMUckU5zi32ozvQqq4PI3bOrw6xUfcbQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.910.0", - "@aws-sdk/nested-clients": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/property-provider": "^4.2.2", - "@smithy/shared-ini-file-loader": "^4.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/types": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.910.0.tgz", - "integrity": "sha512-o67gL3vjf4nhfmuSUNNkit0d62QJEwwHLxucwVJkR/rw9mfUtAWsgBs8Tp16cdUbMgsyQtCQilL8RAJDoGtadQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/util-endpoints": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.910.0.tgz", - "integrity": "sha512-6XgdNe42ibP8zCQgNGDWoOF53RfEKzpU/S7Z29FTTJ7hcZv0SytC0ZNQQZSx4rfBl036YWYwJRoJMlT4AA7q9A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.910.0", - "@smithy/types": "^4.7.1", - "@smithy/url-parser": "^4.2.2", - "@smithy/util-endpoints": "^3.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.910.0.tgz", - "integrity": "sha512-iOdrRdLZHrlINk9pezNZ82P/VxO/UmtmpaOAObUN+xplCUJu31WNM2EE/HccC8PQw6XlAudpdA6HDTGiW6yVGg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.910.0", - "@smithy/types": "^4.7.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.910.0.tgz", - "integrity": "sha512-qNV+rywWQDOOWmGpNlWLCU6zkJurocTBB2uLSdQ8b6Xg6U/i1VTJsoUQ5fbhSQpp/SuBGiIglyB1gSc0th7hPw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.910.0", - "@aws-sdk/types": "3.910.0", - "@smithy/node-config-provider": "^4.3.2", - "@smithy/types": "^4.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/@aws-sdk/xml-builder": { - "version": "3.910.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.910.0.tgz", - "integrity": "sha512-UK0NzRknzUITYlkDibDSgkWvhhC11OLhhhGajl6pYCACup+6QE4SsLvmAGMkyNtGVCJ6Q+BM6PwDCBZyBgwl9A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.7.1", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecommit/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/@aws-sdk/client-ecr": { "version": "3.981.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecr/-/client-ecr-3.981.0.tgz", diff --git a/package.json b/package.json index 47ae8283..711549a2 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ }, "dependencies": { "@aws-sdk/client-codebuild": "^3.907.0", - "@aws-sdk/client-codecommit": "^3.910.0", "@aws-sdk/client-ecr": "^3.981.0", "@aws-sdk/client-iam": "^3.911.0", "@aws-sdk/client-s3": "^3.907.0", diff --git a/src/lib/server/aws/codebuild.ts b/src/lib/server/aws/codebuild.ts index 4c17aeae..3611d121 100644 --- a/src/lib/server/aws/codebuild.ts +++ b/src/lib/server/aws/codebuild.ts @@ -11,8 +11,8 @@ import { } from '@aws-sdk/client-codebuild'; import { SpanStatusCode, trace } from '@opentelemetry/api'; import type { Prisma } from '@prisma/client'; -import { AWSVars } from './vars'; import { S3 } from './s3'; +import { AWSVars } from './vars'; import { type BuildForPrefix, getArtifactFilename, @@ -66,11 +66,9 @@ export class CodeBuild { */ public async startBuild( repoUrl: string, - commitId: string, build: BuildForCodeBuild, buildSpec: string, - versionCode: number, - codeCommit: boolean + versionCode: number ) { return tracer.startActiveSpan(`CodeBuild - StartBuild`, async (span) => { try { @@ -79,7 +77,6 @@ export class CodeBuild { span.setAttributes({ 'code-build.process': buildProcess, 'code-build.repo-url': repoUrl, - 'code-build.commit-id': commitId, 'code-build.job-id': job.id, 'code-build.build-id': build.id, 'code-build.version-code': versionCode @@ -90,124 +87,75 @@ export class CodeBuild { const buildPath = this.getBuildPath(job); const artifactPath = getArtifactPath(job, 'codebuild-output'); span.setAttribute('code-build.artifact-path', artifactPath); - // Leaving all this code together to make it easier to remove when git is no longer supported - if (codeCommit) { - span.addEvent('StartBuild - CodeCommit'); - const res = await this.codeBuildClient.send( - new StartBuildCommand({ - projectName: buildApp, - artifactsOverride: { - location: artifacts_bucket, - name: '/', - namespaceType: 'NONE', - packaging: 'NONE', - path: artifactPath, - type: 'S3' - }, - buildspecOverride: buildSpec, - environmentVariablesOverride: [ - { - name: 'BUILD_NUMBER', - value: String(build.id) - }, - { - name: 'APP_BUILDER_SCRIPT_PATH', - value: buildPath - }, - { - name: 'PUBLISHER', - value: job.publisher_id - }, - { - name: 'VERSION_CODE', - value: '' + versionCode - }, - { - name: 'SECRETS_BUCKET', - value: secretsBucket - } - ], - sourceLocationOverride: repoUrl, - sourceVersion: commitId - }) - ); - const buildId = res.build?.id; - const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); - span.setAttributes({ - 'code-build.build.build-id': buildId, - 'code-build.build.build-guid': buildGuid - }); - return buildGuid; - } else { - span.addEvent('StartBuild - S3'); - const targets = build.targets ?? 'apk play-listing'; - const environmentArray = [ - // BUILD_NUMBER Must be first for tests - { - name: 'BUILD_NUMBER', - value: String(build.id) - }, - { - name: 'APP_BUILDER_SCRIPT_PATH', - value: buildPath - }, - { - name: 'PUBLISHER', - value: job.publisher_id - }, - { - name: 'VERSION_CODE', - value: '' + versionCode - }, - { - name: 'SECRETS_BUCKET', - value: secretsBucket - }, - { - name: 'PROJECT_S3', - value: repoUrl - }, - { - name: 'TARGETS', - value: targets + + span.addEvent('StartBuild - S3'); + const targets = build.targets ?? 'apk play-listing'; + const environmentArray = [ + // BUILD_NUMBER Must be first for tests + { + name: 'BUILD_NUMBER', + value: String(build.id) + }, + { + name: 'APP_BUILDER_SCRIPT_PATH', + value: buildPath + }, + { + name: 'PUBLISHER', + value: job.publisher_id + }, + { + name: 'VERSION_CODE', + value: '' + versionCode + }, + { + name: 'SECRETS_BUCKET', + value: secretsBucket + }, + { + name: 'PROJECT_S3', + value: repoUrl + }, + { + name: 'TARGETS', + value: targets + }, + { + name: 'SCRIPT_S3', + value: AWSVars.scriptsPath() + } + ]; + const adjustedEnvironmentArray = this.addEnvironmentToArray( + environmentArray, + build.environment + ); + const computeType = this.getComputeType(adjustedEnvironmentArray); + const imageTag = this.getImageTag(adjustedEnvironmentArray); + const result = await this.codeBuildClient.send( + new StartBuildCommand({ + projectName: buildApp, + artifactsOverride: { + location: artifacts_bucket, // output bucket + name: '/', // name of output artifact object + namespaceType: 'NONE', + packaging: 'NONE', + path: artifactPath, // path to output artifacts + type: 'S3' // REQUIRED }, - { - name: 'SCRIPT_S3', - value: AWSVars.scriptsPath() - } - ]; - const adjustedEnvironmentArray = this.addEnvironmentToArray( - environmentArray, - build.environment - ); - const computeType = this.getComputeType(adjustedEnvironmentArray); - const imageTag = this.getImageTag(adjustedEnvironmentArray); - const result = await this.codeBuildClient.send( - new StartBuildCommand({ - projectName: buildApp, - artifactsOverride: { - location: artifacts_bucket, // output bucket - name: '/', // name of output artifact object - namespaceType: 'NONE', - packaging: 'NONE', - path: artifactPath, // path to output artifacts - type: 'S3' // REQUIRED - }, - buildspecOverride: buildSpec, - environmentVariablesOverride: adjustedEnvironmentArray, - sourceTypeOverride: 'NO_SOURCE', - computeTypeOverride: computeType, - imageOverride: AWSVars.imageRepo() + ':' + imageTag - }) - ); - const buildId = result.build?.id; - const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); - span.setAttributes({ - 'code-build.build.build-id': buildId, - 'code-build.build.build-guid': buildGuid - }); - return buildGuid; - } + buildspecOverride: buildSpec, + environmentVariablesOverride: adjustedEnvironmentArray, + sourceTypeOverride: 'NO_SOURCE', + computeTypeOverride: computeType, + imageOverride: AWSVars.imageRepo() + ':' + imageTag + }) + ); + const buildId = result.build?.id; + const buildGuid = buildId?.substring(buildId.indexOf(':') + 1); + span.setAttributes({ + 'code-build.build.build-id': buildId, + 'code-build.build.build-guid': buildGuid + }); + return buildGuid; } catch (e) { span.recordException(e as Error); span.setStatus({ diff --git a/src/lib/server/aws/codecommit.ts b/src/lib/server/aws/codecommit.ts deleted file mode 100644 index f3ed3d52..00000000 --- a/src/lib/server/aws/codecommit.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - CodeCommitClient, - GetBranchCommand, - GetRepositoryCommand -} from '@aws-sdk/client-codecommit'; -import { SpanStatusCode, trace } from '@opentelemetry/api'; -import { AWSVars } from './vars'; - -export class CodeCommit { - public codeCommitClient; - - public constructor() { - this.codeCommitClient = CodeCommit.getCodeCommitClient(); - } - /** - * Configure and get the CodeCommit Client - * @return \Aws\CodeBuild\CodeCommitClient - */ - public static getCodeCommitClient() { - const span = trace.getActiveSpan(); - let client: CodeCommitClient | null = null; - try { - client = new CodeCommitClient({ - region: AWSVars.artifactsRegion() - }); - } catch (e) { - span?.recordException(e as Error); - span?.setStatus({ - code: SpanStatusCode.ERROR, - message: (e as Error).message - }); - } finally { - span?.addEvent('CodeCommit - getCodeCommitClient', { - 'code-commit.client.api-version': client?.config.apiVersion, - 'code-commit.client.service-id': client?.config.serviceId - }); - } - return client!; - } - /** - * Returns http url of code commit archive derived from git url needed for CodeBuild - * - * @param string git_url - * @return string http codecommit url - */ - public async getSourceURL(git_url: string) { - const span = trace.getActiveSpan(); - let cloneUrl: string | undefined = undefined; - try { - const repo = git_url.substring(git_url.indexOf('/') + 1); - const repoInfo = await this.codeCommitClient.send( - new GetRepositoryCommand({ - repositoryName: repo - }) - ); - cloneUrl = repoInfo.repositoryMetadata?.cloneUrlHttp; - } catch (e) { - span?.recordException(e as Error); - span?.setStatus({ - code: SpanStatusCode.ERROR, - message: (e as Error).message - }); - } finally { - span?.addEvent('CodeCommit - getSourceURL', { - 'code-commit.git-url': git_url, - 'code-commit.source-url': cloneUrl ?? '' - }); - } - return cloneUrl; - } - /** - * Return ssh url of code commit archive derived from git url needed for CodeBuild - * - * @param string git_url - * @return string http codecommit url - */ - public async getSourceSshURL(git_url: string) { - const span = trace.getActiveSpan(); - let cloneUrl: string | undefined = undefined; - try { - const repo = git_url.substring(git_url.indexOf('/') + 1); - const repoInfo = await this.codeCommitClient.send( - new GetRepositoryCommand({ - repositoryName: repo - }) - ); - cloneUrl = repoInfo.repositoryMetadata?.cloneUrlSsh; - } catch (e) { - span?.recordException(e as Error); - span?.setStatus({ - code: SpanStatusCode.ERROR, - message: (e as Error).message - }); - } finally { - span?.addEvent('CodeCommit - getSourceSshURL', { - 'code-commit.git-url': git_url, - 'code-commit.source-ssh-url': cloneUrl ?? '' - }); - } - return cloneUrl; - } - - /** - * Returns commit id of the specified branch for the specified repo - * - * @param string git_url - * @param string branch - * @return string commit id - */ - public async getCommitId(git_url: string, branch: string) { - const span = trace.getActiveSpan(); - let commitId: string | undefined = undefined; - try { - const repo = git_url.substring(git_url.indexOf('/') + 1); - const result = await this.codeCommitClient.send( - new GetBranchCommand({ - branchName: branch, - repositoryName: repo - }) - ); - commitId = result.branch?.commitId; - } catch (e) { - span?.recordException(e as Error); - span?.setStatus({ - code: SpanStatusCode.ERROR, - message: (e as Error).message - }); - } finally { - span?.addEvent('CodeCommit - getCommitId', { - 'code-commit.git-url': git_url, - 'code-commit.commit-id': commitId ?? '' - }); - } - return commitId; - } -} diff --git a/src/lib/server/job-executors/build.ts b/src/lib/server/job-executors/build.ts index 6213b51f..b4d855fb 100644 --- a/src/lib/server/job-executors/build.ts +++ b/src/lib/server/job-executors/build.ts @@ -3,7 +3,6 @@ import type { Job } from 'bullmq'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { CodeBuild } from '../aws/codebuild'; -import { CodeCommit } from '../aws/codecommit'; import { S3 } from '../aws/s3'; import { AWSVars } from '../aws/vars'; import { BullMQ, getQueues } from '../bullmq'; @@ -24,113 +23,45 @@ export async function product(job: Job): Promise job.updateProgress(10); const gitUrl = build.job.git_url; - // Check to see if codebuild project - const codeCommitProject = gitUrl.startsWith('ssh://'); - if (codeCommitProject) { - job.log('Starting build with CodeCommit'); - // Left this block intact to make it easier to remove when codecommit is not supported - const codecommit = new CodeCommit(); - const branch = 'master'; - const repoUrl = await codecommit.getSourceURL(gitUrl); - if (!repoUrl) throw new Error('No repoUrl found!'); - const commitId = await codecommit.getCommitId(gitUrl, branch); - if (!commitId) throw new Error('No commitId found!'); - job.updateProgress(25); - const script = ( - await readFile(join(process.cwd(), './scripts/appbuilders_build.yml')) - ).toString(); - job.updateProgress(50); - // Start the build - const codeBuild = new CodeBuild(); - const versionCode = (await getVersionCode(build.job)) + 1; - const lastBuildGuid = await codeBuild.startBuild( - repoUrl, - commitId, - build, - script, - versionCode, - codeCommitProject - ); - job.updateProgress(75); - if (lastBuildGuid) { - await prisma.build.update({ - where: { id: build.id }, - data: trimStrings( - { - build_guid: lastBuildGuid, - codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), - console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), - status: Build.Status.Active - }, - 'build', - job.log - ) - }); - } - const name = pollName(build.id); - await getQueues().Polling.upsertJobScheduler(name, BullMQ.RepeatEveryMinute, { - name, - data: { - type: BullMQ.JobType.Poll_Build, - buildId: build.id - } - }); - job.updateProgress(100); - return { - repoUrl, - commitId, - versionCode, - lastBuildGuid - }; - } else { - job.log('Starting build with CodeBuild'); - const script = ( - await readFile(join(process.cwd(), './scripts/appbuilders_s3_build.yml')) - ).toString(); - job.updateProgress(50); - // Start the build - const codeBuild = new CodeBuild(); - const commitId = ''; // TODO: Remove when git is removed - const versionCode = await getVersionCode(build.job); // Is there a reason this is not incremented here?? - const lastBuildGuid = await codeBuild.startBuild( - gitUrl, - commitId, - build, - script, - versionCode, - codeCommitProject - ); - job.updateProgress(75); - if (lastBuildGuid) { - await prisma.build.update({ - where: { id: build.id }, - data: trimStrings( - { - build_guid: lastBuildGuid, - codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), - console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), - status: Build.Status.Active - }, - 'build', - job.log - ) - }); - } - const name = pollName(build.id); - await getQueues().Polling.upsertJobScheduler(name, BullMQ.RepeatEveryMinute, { - name, - data: { - type: BullMQ.JobType.Poll_Build, - buildId: build.id - } + job.log('Starting build with CodeBuild'); + const script = ( + await readFile(join(process.cwd(), './scripts/appbuilders_s3_build.yml')) + ).toString(); + job.updateProgress(50); + // Start the build + const codeBuild = new CodeBuild(); + const versionCode = await getVersionCode(build.job); // Is there a reason this is not incremented here?? + const lastBuildGuid = await codeBuild.startBuild(gitUrl, build, script, versionCode); + job.updateProgress(75); + if (lastBuildGuid) { + await prisma.build.update({ + where: { id: build.id }, + data: trimStrings( + { + build_guid: lastBuildGuid, + codebuild_url: CodeBuild.getCodeBuildUrl('build_app', lastBuildGuid), + console_text_url: CodeBuild.getConsoleTextUrl('build_app', lastBuildGuid), + status: Build.Status.Active + }, + 'build', + job.log + ) }); - job.updateProgress(100); - return { - versionCode, - lastBuildGuid - }; } + const name = pollName(build.id); + await getQueues().Polling.upsertJobScheduler(name, BullMQ.RepeatEveryMinute, { + name, + data: { + type: BullMQ.JobType.Poll_Build, + buildId: build.id + } + }); + job.updateProgress(100); + return { + versionCode, + lastBuildGuid + }; } catch (e) { job.log(`${e}`); await prisma.build.update({ From 51793c1222cc2b2e2e3da650299485200665640a Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Fri, 27 Feb 2026 13:23:05 -0600 Subject: [PATCH 129/144] Fix run.sh --- run | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run b/run index 4b8bc606..c3ee409d 100755 --- a/run +++ b/run @@ -19,7 +19,7 @@ function runstuff { up:build) ${COMPOSE} up --build $arguments ;; up) ${COMPOSE} up $arguments ;; local) - runstuff dc up -d db stg-tunnel valkey + runstuff dc up -d db valkey ;; down) ${COMPOSE} down $arguments From 1847e35b9710c9035d2582306ad2ea43a4618405 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Fri, 27 Feb 2026 13:27:55 -0600 Subject: [PATCH 130/144] Remove old code --- .dockerignore | 2 - .prettierignore | 4 - Makefile | 55 - Vagrantfile | 141 - application/.bowerrc | 3 - application/.gitignore | 31 - application/LICENSE.md | 32 - application/README.md | 97 - application/backend/assets/AppAsset.php | 29 - application/backend/config/.gitignore | 2 - application/backend/config/bootstrap.php | 1 - application/backend/config/main.php | 34 - application/backend/config/params.php | 4 - .../backend/controllers/SiteController.php | 83 - application/backend/models/.gitkeep | 1 - application/backend/runtime/.gitignore | 2 - application/backend/views/layouts/main.php | 71 - application/backend/views/site/error.php | 27 - application/backend/views/site/index.php | 52 - application/backend/views/site/login.php | 29 - application/backend/web/.gitignore | 1 - application/backend/web/assets/.gitignore | 2 - application/backend/web/css/site.css | 91 - application/backend/web/favicon.ico | Bin 318 -> 0 bytes application/backend/web/index-test.php | 19 - application/backend/web/index.php | 18 - application/backend/web/robots.txt | 2 - application/codeception.yml | 17 - application/common/components/AWSCommon.php | 83 - .../common/components/Appbuilder_logger.php | 133 - application/common/components/CodeBuild.php | 544 -- application/common/components/CodeCommit.php | 90 - application/common/components/EmailUtils.php | 255 - application/common/components/FileUtils.php | 51 - application/common/components/IAmWrapper.php | 258 - .../common/components/JenkinsUtils.php | 173 - application/common/components/S3.php | 388 -- application/common/components/STS.php | 176 - application/common/config/.gitignore | 2 - application/common/config/bootstrap.php | 6 - application/common/config/main.php | 87 - application/common/helpers/Utils.php | 101 - .../common/interfaces/ArtifactsProvider.php | 11 - application/common/mail/layouts/html.php | 22 - application/common/mail/layouts/text.php | 12 - .../mail/operations/Test/enduser-testmsg.php | 10 - .../common/mail/passwordResetToken-html.php | 15 - .../common/mail/passwordResetToken-text.php | 12 - application/common/models/Build.php | 679 -- application/common/models/BuildBase.php | 97 - application/common/models/Client.php | 56 - application/common/models/ClientBase.php | 71 - application/common/models/EmailQueue.php | 35 - application/common/models/EmailQueueBase.php | 65 - application/common/models/Job.php | 266 - application/common/models/JobBase.php | 86 - application/common/models/LoginForm.php | 78 - application/common/models/OperationQueue.php | 205 - .../common/models/OperationQueueBase.php | 65 - application/common/models/OriginalUser.php | 188 - application/common/models/Project.php | 248 - application/common/models/ProjectBase.php | 82 - application/common/models/Release.php | 282 - application/common/models/ReleaseBase.php | 92 - application/common/models/User.php | 50 - .../common/preview/playlisting/index.html | 548 -- application/composer.json | 33 - application/composer.lock | 5613 ----------------- .../console/components/ActionCommon.php | 36 - .../console/components/AwsStartupAction.php | 135 - .../components/CopyErrorToS3Operation.php | 58 - .../console/components/CopyToS3Operation.php | 234 - .../console/components/DevelopmentAction.php | 483 -- .../console/components/ManageBuildsAction.php | 240 - .../components/ManageProjectsAction.php | 268 - .../components/ManageReleasesAction.php | 232 - .../MaxRetriesExceededException.php | 16 - .../console/components/OperationInterface.php | 12 - .../components/OperationsQueueAction.php | 136 - .../components/ProjectUpdateOperation.php | 90 - .../components/RemoveExpiredBuildsAction.php | 39 - .../components/S3MaintenanceAction.php | 30 - application/console/config/.gitignore | 2 - application/console/config/bootstrap.php | 1 - application/console/config/main.php | 82 - application/console/config/params.php | 4 - application/console/controllers/.gitkeep | 0 .../console/controllers/CronController.php | 244 - .../console/migrations-local/.gitignore | 2 - application/console/migrations/.gitkeep | 0 .../m150903_174303_create_job_table.php | 39 - .../m150914_144408_create_build_table.php | 41 - ...17_163901_add_artifact_url_base_to_job.php | 16 - .../m151005_142150_rename_build_columns.php | 19 - ...1106_195737_change_job_request_id_type.php | 17 - .../m151117_175414_add_channel_to_build.php | 17 - ...17_202114_remove_artifact_url_from_job.php | 17 - .../m160104_194709_create_publish_table.php | 26 - ...60108_213227_rename_publish_to_release.php | 34 - ...60108_213259_remove_channel_from_build.php | 18 - ...0804_add_build_number_to_release_table.php | 17 - ...m160112_220005_alter_error_to_hold_url.php | 19 - ...43_add_channel_version_number_to_build.php | 19 - ...160204_143306_create_email_queue_table.php | 29 - ...03_200821_create_operation_queue_table.php | 31 - .../m160427_162531_create_client_table.php | 24 - .../m160427_182255_add_client_to_job.php | 20 - ...15_174530_change_artifact_url_of_build.php | 20 - ...200721_add_initial_version_code_to_job.php | 20 - ...hange_initial_to_existing_version_code.php | 17 - .../m160708_134340_add_jenkins_url_to_job.php | 19 - ...926_142214_add_promote_from_to_release.php | 17 - .../m161024_184204_create_project_table.php | 33 - ...161102_191552_add_client_id_to_project.php | 20 - ...1108_135607_alter_project_error_column.php | 18 - ...4204_change_build_number_to_build_guid.php | 22 - ...build_number_to_build_guid_for_release.php | 22 - .../m180629_183318_addConsoleTextUrl.php | 23 - .../m180704_191855_addCodeBuildUrl.php | 23 - ...m190308_210456_add_target_env_to_build.php | 23 - ...90311_200559_add_target_env_to_release.php | 22 - ...190424_142509_add_artifacts_to_release.php | 24 - ...m200128_220041_alter_build_environment.php | 20 - .../m200616_193900_alter_multiple_apks.php | 19 - ...10825_131342_alter_release_environment.php | 20 - application/console/models/.gitkeep | 1 - application/console/runtime/.gitignore | 2 - .../views/cron/scripts/appbuilders_build.php | 72 - .../views/cron/scripts/appbuilders_build.yml | 66 - .../cron/scripts/appbuilders_publish.php | 52 - .../cron/scripts/appbuilders_publish.yml | 46 - .../cron/scripts/appbuilders_s3_build.php | 52 - .../cron/scripts/appbuilders_s3_build.yml | 46 - .../project_default/default/default.zip | Bin 284 -> 0 bytes .../scripts/scriptureappbuilder/google.groovy | 17 - .../scripts/scriptureappbuilder/jobs.groovy | 155 - .../scriptureappbuilder/keystore.groovy | 30 - .../scripts/scriptureappbuilder_build.php | 14 - .../scripts/scriptureappbuilder_publish.php | 14 - .../cron/scripts/upload/default/build.sh | 711 --- .../cron/scripts/upload/default/publish.sh | 512 -- .../cron/scripts/utilities/Helper.groovy | 19 - .../dev/backend/config/main-local.php | 21 - .../dev/backend/config/params-local.php | 3 - .../dev/backend/web/index-test.php | 19 - .../environments/dev/backend/web/index.php | 18 - .../dev/common/config/main-local.php | 20 - .../dev/common/config/params-local.php | 3 - .../dev/console/config/main-local.php | 3 - .../dev/console/config/params-local.php | 3 - .../dev/frontend/config/main-local.php | 21 - .../dev/frontend/config/params-local.php | 3 - .../dev/frontend/web/index-test.php | 18 - .../environments/dev/frontend/web/index.php | 18 - application/environments/dev/yii | 32 - application/environments/index.php | 61 - .../prod/backend/config/main-local.php | 9 - .../prod/backend/config/params-local.php | 3 - .../environments/prod/backend/web/index.php | 18 - .../prod/common/config/main-local.php | 16 - .../prod/common/config/params-local.php | 3 - .../prod/console/config/main-local.php | 3 - .../prod/console/config/params-local.php | 3 - .../prod/frontend/config/main-local.php | 9 - .../prod/frontend/config/params-local.php | 3 - .../environments/prod/frontend/web/index.php | 18 - application/environments/prod/yii | 32 - application/frontend/assets/AppAsset.php | 29 - .../components/JobControllerUtils.php | 124 - application/frontend/config/.gitignore | 2 - application/frontend/config/bootstrap.php | 1 - application/frontend/config/main.php | 97 - .../controllers/BuildAdminController.php | 139 - .../controllers/ClientAdminController.php | 122 - .../controllers/JobAdminController.php | 122 - .../frontend/controllers/JobController.php | 142 - .../OperationQueueAdminController.php | 122 - .../controllers/ProjectAdminController.php | 123 - .../controllers/ProjectController.php | 136 - .../controllers/ReleaseAdminController.php | 122 - .../frontend/controllers/SiteController.php | 171 - .../frontend/controllers/SystemController.php | 354 -- application/frontend/models/ContactForm.php | 61 - .../models/PasswordResetRequestForm.php | 60 - .../frontend/models/ResetPasswordForm.php | 65 - application/frontend/models/SignupForm.php | 58 - application/frontend/runtime/.gitignore | 2 - .../frontend/views/build-admin/_form.php | 47 - .../frontend/views/build-admin/create.php | 21 - .../frontend/views/build-admin/index.php | 52 - .../frontend/views/build-admin/update.php | 21 - .../frontend/views/build-admin/view.php | 71 - .../frontend/views/client-admin/_form.php | 29 - .../frontend/views/client-admin/create.php | 21 - .../frontend/views/client-admin/index.php | 35 - .../frontend/views/client-admin/update.php | 21 - .../frontend/views/client-admin/view.php | 39 - .../frontend/views/job-admin/_form.php | 41 - .../frontend/views/job-admin/create.php | 21 - .../frontend/views/job-admin/index.php | 45 - .../frontend/views/job-admin/update.php | 21 - application/frontend/views/job-admin/view.php | 57 - application/frontend/views/layouts/main.php | 77 - .../views/operation-queue-admin/_form.php | 41 - .../views/operation-queue-admin/create.php | 21 - .../views/operation-queue-admin/index.php | 41 - .../views/operation-queue-admin/update.php | 21 - .../views/operation-queue-admin/view.php | 45 - .../frontend/views/project-admin/_form.php | 47 - .../frontend/views/project-admin/create.php | 21 - .../frontend/views/project-admin/index.php | 41 - .../frontend/views/project-admin/update.php | 21 - .../frontend/views/project-admin/view.php | 52 - .../frontend/views/release-admin/_form.php | 51 - .../frontend/views/release-admin/create.php | 21 - .../frontend/views/release-admin/index.php | 65 - .../frontend/views/release-admin/update.php | 21 - .../frontend/views/release-admin/view.php | 63 - application/frontend/views/site/about.php | 28 - application/frontend/views/site/contact.php | 34 - application/frontend/views/site/error.php | 27 - application/frontend/views/site/index.php | 73 - application/frontend/views/site/login.php | 32 - .../views/site/requestPasswordResetToken.php | 27 - .../frontend/views/site/resetPassword.php | 27 - application/frontend/views/site/signup.php | 29 - application/frontend/web/.gitignore | 0 application/frontend/web/.htaccess | 6 - .../frontend/web/SILLogoBlue132x184.png | Bin 12602 -> 0 bytes application/frontend/web/assets/.gitignore | 2 - application/frontend/web/css/site.css | 91 - application/frontend/web/favicon.ico | Bin 318 -> 0 bytes application/frontend/web/index-test.php | 18 - application/frontend/web/index.php | 16 - application/frontend/web/robots.txt | 2 - application/frontend/widgets/Alert.php | 79 - application/init | 205 - application/init.bat | 20 - application/quickstart.php | 96 - application/rebuildbasemodels.sh | 19 - application/requirements.php | 132 - application/run-cron.sh | 40 - application/run-tests.sh | 17 - application/run.sh | 27 - application/tests/README.md | 58 - application/tests/_bootstrap.php | 17 - application/tests/_data/dump.sql | 1 - .../tests/_support/AcceptanceHelper.php | 10 - .../tests/_support/AcceptanceTester.php | 26 - .../tests/_support/FunctionalHelper.php | 10 - .../tests/_support/FunctionalTester.php | 26 - application/tests/_support/UnitHelper.php | 10 - application/tests/_support/UnitTester.php | 26 - .../_generated/AcceptanceTesterActions.php | 2382 ------- .../_generated/FunctionalTesterActions.php | 415 -- .../_support/_generated/UnitTesterActions.php | 609 -- application/tests/acceptance.suite.yml | 14 - .../tests/acceptance/AcceptanceTester.php | 1973 ------ application/tests/acceptance/_bootstrap.php | 2 - application/tests/codeception.yml | 11 - .../tests/codeception/_output/.gitignore | 2 - .../tests/codeception/backend/.gitignore | 4 - .../tests/codeception/backend/_bootstrap.php | 23 - .../codeception/backend/_output/.gitignore | 2 - .../codeception/backend/acceptance.suite.yml | 28 - .../backend/acceptance/LoginCept.php | 42 - .../backend/acceptance/_bootstrap.php | 2 - .../tests/codeception/backend/codeception.yml | 17 - .../codeception/backend/functional.suite.yml | 17 - .../backend/functional/LoginCept.php | 28 - .../backend/functional/_bootstrap.php | 2 - .../tests/codeception/backend/unit.suite.yml | 6 - .../codeception/backend/unit/DbTestCase.php | 8 - .../codeception/backend/unit/TestCase.php | 8 - .../codeception/backend/unit/_bootstrap.php | 2 - .../backend/unit/fixtures/data/.gitkeep | 0 .../tests/codeception/bin/_bootstrap.php | 23 - application/tests/codeception/bin/yii | 33 - application/tests/codeception/bin/yii.bat | 20 - .../tests/codeception/common/.gitignore | 4 - .../tests/codeception/common/_bootstrap.php | 15 - .../codeception/common/_output/.gitignore | 2 - .../codeception/common/_pages/LoginPage.php | 25 - .../common/_support/FixtureHelper.php | 60 - .../tests/codeception/common/codeception.yml | 13 - .../common/fixtures/UserFixture.php | 13 - .../common/fixtures/data/init_login.php | 14 - .../common/templates/fixtures/user.php | 17 - .../tests/codeception/common/unit.suite.yml | 6 - .../codeception/common/unit/DbTestCase.php | 11 - .../codeception/common/unit/TestCase.php | 11 - .../codeception/common/unit/_bootstrap.php | 2 - .../common/unit/fixtures/data/models/user.php | 14 - .../common/unit/models/LoginFormTest.php | 94 - .../tests/codeception/config/acceptance.php | 7 - .../codeception/config/backend/acceptance.php | 17 - .../codeception/config/backend/config.php | 5 - .../codeception/config/backend/functional.php | 18 - .../tests/codeception/config/backend/unit.php | 16 - .../tests/codeception/config/common/unit.php | 27 - .../tests/codeception/config/config.php | 109 - .../tests/codeception/config/console/unit.php | 14 - .../config/frontend/acceptance.php | 17 - .../codeception/config/frontend/config.php | 5 - .../config/frontend/functional.php | 18 - .../codeception/config/frontend/unit.php | 16 - .../tests/codeception/config/functional.php | 18 - application/tests/codeception/config/unit.php | 7 - .../tests/codeception/console/.gitignore | 2 - .../tests/codeception/console/_bootstrap.php | 16 - .../codeception/console/_output/.gitignore | 2 - .../tests/codeception/console/codeception.yml | 13 - .../tests/codeception/console/unit.suite.yml | 6 - .../codeception/console/unit/DbTestCase.php | 11 - .../codeception/console/unit/TestCase.php | 11 - .../codeception/console/unit/_bootstrap.php | 2 - .../console/unit/fixtures/data/.gitkeep | 0 .../tests/codeception/frontend/.gitignore | 4 - .../tests/codeception/frontend/_bootstrap.php | 23 - .../codeception/frontend/_output/.gitignore | 2 - .../codeception/frontend/_pages/AboutPage.php | 14 - .../frontend/_pages/ContactPage.php | 26 - .../frontend/_pages/SignupPage.php | 27 - .../codeception/frontend/acceptance.suite.yml | 28 - .../frontend/acceptance/AboutCept.php | 8 - .../frontend/acceptance/ContactCept.php | 54 - .../frontend/acceptance/HomeCept.php | 10 - .../frontend/acceptance/LoginCept.php | 32 - .../frontend/acceptance/SignupCest.php | 82 - .../frontend/acceptance/_bootstrap.php | 2 - .../codeception/frontend/codeception.yml | 17 - .../codeception/frontend/functional.suite.yml | 17 - .../frontend/functional/AboutCept.php | 8 - .../frontend/functional/ContactCept.php | 45 - .../frontend/functional/HomeCept.php | 9 - .../frontend/functional/LoginCept.php | 27 - .../frontend/functional/SignupCest.php | 90 - .../frontend/functional/_bootstrap.php | 3 - .../tests/codeception/frontend/unit.suite.yml | 6 - .../codeception/frontend/unit/DbTestCase.php | 11 - .../codeception/frontend/unit/TestCase.php | 11 - .../codeception/frontend/unit/_bootstrap.php | 2 - .../unit/fixtures/data/models/user.php | 23 - .../frontend/unit/models/ContactFormTest.php | 59 - .../models/PasswordResetRequestFormTest.php | 88 - .../unit/models/ResetPasswordFormTest.php | 44 - .../frontend/unit/models/SignupFormTest.php | 53 - application/tests/functional.suite.yml | 9 - .../tests/functional/FunctionalTester.php | 360 -- application/tests/functional/_bootstrap.php | 2 - .../aws/codebuild/MockCodeBuildClient.php | 96 - .../tests/mock/aws/codebuild/MockPromise.php | 49 - .../aws/codecommit/MockCodeCommitClient.php | 58 - .../tests/mock/aws/iam/MockIAmGroup.php | 8 - .../tests/mock/aws/iam/MockIAmResult.php | 12 - .../tests/mock/aws/iam/MockIAmUser.php | 12 - .../tests/mock/aws/iam/MockIamClient.php | 102 - .../tests/mock/aws/s3/MockS3Client.php | 166 - .../tests/mock/aws/sts/MockStsClient.php | 28 - .../components/MockAppbuilder_logger.php | 33 - .../mock/common/components/MockFileUtils.php | 90 - .../mock/common/components/MockIAmWrapper.php | 77 - .../common/components/MockJenkinsUtils.php | 94 - .../controllers/MockCronController.php | 17 - .../mock/gitWrapper/MockGitWorkingCopy.php | 90 - .../tests/mock/gitWrapper/MockGitWrapper.php | 77 - .../tests/mock/jenkins/MockJenkins.php | 19 - .../tests/mock/jenkins/MockJenkinsBuild.php | 75 - .../tests/mock/jenkins/MockJenkinsJob.php | 82 - application/tests/mock/mailer/MockMailer.php | 93 - application/tests/unit.suite.yml | 6 - application/tests/unit/UnitTestBase.php | 36 - application/tests/unit/UnitTester.php | 300 - application/tests/unit/_bootstrap.php | 2 - .../unit/common/components/BuildTest.php | 102 - .../unit/common/components/CodeBuildTest.php | 318 - .../unit/common/components/EmailUtilsTest.php | 232 - .../unit/common/components/IAmWrapperTest.php | 106 - .../unit/common/components/ProjectTest.php | 76 - .../tests/unit/common/components/S3Test.php | 157 - .../tests/unit/common/components/STSTest.php | 66 - .../components/CopyErrorToS3OperationTest.php | 49 - .../components/CopyToS3OperationTest.php | 55 - .../components/ManageBuildActionTest.php | 155 - .../components/ManageProjectsActionTest.php | 65 - .../components/ManageReleasesActionTest.php | 94 - .../components/ProjectUpdateOperationTest.php | 88 - .../RemoveExpiredBuildsActionTest.php | 47 - .../components/S3MaintenanceActionTest.php | 51 - .../fixtures/common/models/BuildFixture.php | 13 - .../common/models/EmailQueueFixture.php | 12 - .../fixtures/common/models/JobFixture.php | 10 - .../common/models/OperationQueueFixture.php | 10 - .../fixtures/common/models/ProjectFixture.php | 11 - .../fixtures/common/models/ReleaseFixture.php | 13 - .../fixtures/data/common/models/Build.php | 292 - .../data/common/models/EmailQueue.php | 21 - .../unit/fixtures/data/common/models/Job.php | 57 - .../data/common/models/OperationQueue.php | 4 - .../fixtures/data/common/models/Project.php | 102 - .../fixtures/data/common/models/Release.php | 112 - .../components/JobControllerUtilsTest.php | 136 - application/yii | 30 - application/yii.bat | 20 - build/appbuilder-cron | 10 - build/appbuilder.conf | 21 - build/logentries.all.crt | 91 - build/rsyslog.conf | 71 - build/sab | 63 - common.env | 7 - docker-compose.yml | 131 - static/SILLogoBlue132x184.png | Bin 12602 -> 0 bytes 412 files changed, 33937 deletions(-) delete mode 100644 Makefile delete mode 100644 Vagrantfile delete mode 100644 application/.bowerrc delete mode 100644 application/.gitignore delete mode 100644 application/LICENSE.md delete mode 100644 application/README.md delete mode 100644 application/backend/assets/AppAsset.php delete mode 100644 application/backend/config/.gitignore delete mode 100644 application/backend/config/bootstrap.php delete mode 100644 application/backend/config/main.php delete mode 100644 application/backend/config/params.php delete mode 100644 application/backend/controllers/SiteController.php delete mode 100644 application/backend/models/.gitkeep delete mode 100644 application/backend/runtime/.gitignore delete mode 100644 application/backend/views/layouts/main.php delete mode 100644 application/backend/views/site/error.php delete mode 100644 application/backend/views/site/index.php delete mode 100644 application/backend/views/site/login.php delete mode 100644 application/backend/web/.gitignore delete mode 100644 application/backend/web/assets/.gitignore delete mode 100644 application/backend/web/css/site.css delete mode 100644 application/backend/web/favicon.ico delete mode 100644 application/backend/web/index-test.php delete mode 100644 application/backend/web/index.php delete mode 100644 application/backend/web/robots.txt delete mode 100644 application/codeception.yml delete mode 100644 application/common/components/AWSCommon.php delete mode 100644 application/common/components/Appbuilder_logger.php delete mode 100644 application/common/components/CodeBuild.php delete mode 100644 application/common/components/CodeCommit.php delete mode 100644 application/common/components/EmailUtils.php delete mode 100644 application/common/components/FileUtils.php delete mode 100644 application/common/components/IAmWrapper.php delete mode 100644 application/common/components/JenkinsUtils.php delete mode 100644 application/common/components/S3.php delete mode 100644 application/common/components/STS.php delete mode 100644 application/common/config/.gitignore delete mode 100644 application/common/config/bootstrap.php delete mode 100644 application/common/config/main.php delete mode 100644 application/common/helpers/Utils.php delete mode 100644 application/common/interfaces/ArtifactsProvider.php delete mode 100644 application/common/mail/layouts/html.php delete mode 100644 application/common/mail/layouts/text.php delete mode 100644 application/common/mail/operations/Test/enduser-testmsg.php delete mode 100644 application/common/mail/passwordResetToken-html.php delete mode 100644 application/common/mail/passwordResetToken-text.php delete mode 100644 application/common/models/Build.php delete mode 100644 application/common/models/BuildBase.php delete mode 100644 application/common/models/Client.php delete mode 100644 application/common/models/ClientBase.php delete mode 100644 application/common/models/EmailQueue.php delete mode 100644 application/common/models/EmailQueueBase.php delete mode 100644 application/common/models/Job.php delete mode 100644 application/common/models/JobBase.php delete mode 100644 application/common/models/LoginForm.php delete mode 100644 application/common/models/OperationQueue.php delete mode 100644 application/common/models/OperationQueueBase.php delete mode 100644 application/common/models/OriginalUser.php delete mode 100644 application/common/models/Project.php delete mode 100644 application/common/models/ProjectBase.php delete mode 100644 application/common/models/Release.php delete mode 100644 application/common/models/ReleaseBase.php delete mode 100644 application/common/models/User.php delete mode 100644 application/common/preview/playlisting/index.html delete mode 100644 application/composer.json delete mode 100644 application/composer.lock delete mode 100644 application/console/components/ActionCommon.php delete mode 100644 application/console/components/AwsStartupAction.php delete mode 100644 application/console/components/CopyErrorToS3Operation.php delete mode 100644 application/console/components/CopyToS3Operation.php delete mode 100644 application/console/components/DevelopmentAction.php delete mode 100644 application/console/components/ManageBuildsAction.php delete mode 100644 application/console/components/ManageProjectsAction.php delete mode 100644 application/console/components/ManageReleasesAction.php delete mode 100644 application/console/components/MaxRetriesExceededException.php delete mode 100644 application/console/components/OperationInterface.php delete mode 100644 application/console/components/OperationsQueueAction.php delete mode 100644 application/console/components/ProjectUpdateOperation.php delete mode 100644 application/console/components/RemoveExpiredBuildsAction.php delete mode 100644 application/console/components/S3MaintenanceAction.php delete mode 100644 application/console/config/.gitignore delete mode 100644 application/console/config/bootstrap.php delete mode 100644 application/console/config/main.php delete mode 100644 application/console/config/params.php delete mode 100644 application/console/controllers/.gitkeep delete mode 100644 application/console/controllers/CronController.php delete mode 100644 application/console/migrations-local/.gitignore delete mode 100644 application/console/migrations/.gitkeep delete mode 100644 application/console/migrations/m150903_174303_create_job_table.php delete mode 100644 application/console/migrations/m150914_144408_create_build_table.php delete mode 100644 application/console/migrations/m150917_163901_add_artifact_url_base_to_job.php delete mode 100644 application/console/migrations/m151005_142150_rename_build_columns.php delete mode 100644 application/console/migrations/m151106_195737_change_job_request_id_type.php delete mode 100644 application/console/migrations/m151117_175414_add_channel_to_build.php delete mode 100644 application/console/migrations/m151217_202114_remove_artifact_url_from_job.php delete mode 100644 application/console/migrations/m160104_194709_create_publish_table.php delete mode 100644 application/console/migrations/m160108_213227_rename_publish_to_release.php delete mode 100644 application/console/migrations/m160108_213259_remove_channel_from_build.php delete mode 100644 application/console/migrations/m160112_160804_add_build_number_to_release_table.php delete mode 100644 application/console/migrations/m160112_220005_alter_error_to_hold_url.php delete mode 100644 application/console/migrations/m160119_135843_add_channel_version_number_to_build.php delete mode 100644 application/console/migrations/m160204_143306_create_email_queue_table.php delete mode 100644 application/console/migrations/m160303_200821_create_operation_queue_table.php delete mode 100644 application/console/migrations/m160427_162531_create_client_table.php delete mode 100644 application/console/migrations/m160427_182255_add_client_to_job.php delete mode 100644 application/console/migrations/m160615_174530_change_artifact_url_of_build.php delete mode 100644 application/console/migrations/m160630_200721_add_initial_version_code_to_job.php delete mode 100644 application/console/migrations/m160707_142745_change_initial_to_existing_version_code.php delete mode 100644 application/console/migrations/m160708_134340_add_jenkins_url_to_job.php delete mode 100644 application/console/migrations/m160926_142214_add_promote_from_to_release.php delete mode 100644 application/console/migrations/m161024_184204_create_project_table.php delete mode 100644 application/console/migrations/m161102_191552_add_client_id_to_project.php delete mode 100644 application/console/migrations/m161108_135607_alter_project_error_column.php delete mode 100644 application/console/migrations/m180613_134204_change_build_number_to_build_guid.php delete mode 100644 application/console/migrations/m180625_151653_change_build_number_to_build_guid_for_release.php delete mode 100644 application/console/migrations/m180629_183318_addConsoleTextUrl.php delete mode 100644 application/console/migrations/m180704_191855_addCodeBuildUrl.php delete mode 100644 application/console/migrations/m190308_210456_add_target_env_to_build.php delete mode 100644 application/console/migrations/m190311_200559_add_target_env_to_release.php delete mode 100644 application/console/migrations/m190424_142509_add_artifacts_to_release.php delete mode 100644 application/console/migrations/m200128_220041_alter_build_environment.php delete mode 100644 application/console/migrations/m200616_193900_alter_multiple_apks.php delete mode 100644 application/console/migrations/m210825_131342_alter_release_environment.php delete mode 100644 application/console/models/.gitkeep delete mode 100644 application/console/runtime/.gitignore delete mode 100644 application/console/views/cron/scripts/appbuilders_build.php delete mode 100644 application/console/views/cron/scripts/appbuilders_build.yml delete mode 100644 application/console/views/cron/scripts/appbuilders_publish.php delete mode 100644 application/console/views/cron/scripts/appbuilders_publish.yml delete mode 100644 application/console/views/cron/scripts/appbuilders_s3_build.php delete mode 100644 application/console/views/cron/scripts/appbuilders_s3_build.yml delete mode 100644 application/console/views/cron/scripts/project_default/default/default.zip delete mode 100644 application/console/views/cron/scripts/scriptureappbuilder/google.groovy delete mode 100644 application/console/views/cron/scripts/scriptureappbuilder/jobs.groovy delete mode 100644 application/console/views/cron/scripts/scriptureappbuilder/keystore.groovy delete mode 100644 application/console/views/cron/scripts/scriptureappbuilder_build.php delete mode 100644 application/console/views/cron/scripts/scriptureappbuilder_publish.php delete mode 100644 application/console/views/cron/scripts/upload/default/build.sh delete mode 100644 application/console/views/cron/scripts/upload/default/publish.sh delete mode 100644 application/console/views/cron/scripts/utilities/Helper.groovy delete mode 100644 application/environments/dev/backend/config/main-local.php delete mode 100644 application/environments/dev/backend/config/params-local.php delete mode 100644 application/environments/dev/backend/web/index-test.php delete mode 100644 application/environments/dev/backend/web/index.php delete mode 100644 application/environments/dev/common/config/main-local.php delete mode 100644 application/environments/dev/common/config/params-local.php delete mode 100644 application/environments/dev/console/config/main-local.php delete mode 100644 application/environments/dev/console/config/params-local.php delete mode 100644 application/environments/dev/frontend/config/main-local.php delete mode 100644 application/environments/dev/frontend/config/params-local.php delete mode 100644 application/environments/dev/frontend/web/index-test.php delete mode 100644 application/environments/dev/frontend/web/index.php delete mode 100644 application/environments/dev/yii delete mode 100644 application/environments/index.php delete mode 100644 application/environments/prod/backend/config/main-local.php delete mode 100644 application/environments/prod/backend/config/params-local.php delete mode 100644 application/environments/prod/backend/web/index.php delete mode 100644 application/environments/prod/common/config/main-local.php delete mode 100644 application/environments/prod/common/config/params-local.php delete mode 100644 application/environments/prod/console/config/main-local.php delete mode 100644 application/environments/prod/console/config/params-local.php delete mode 100644 application/environments/prod/frontend/config/main-local.php delete mode 100644 application/environments/prod/frontend/config/params-local.php delete mode 100644 application/environments/prod/frontend/web/index.php delete mode 100644 application/environments/prod/yii delete mode 100644 application/frontend/assets/AppAsset.php delete mode 100644 application/frontend/components/JobControllerUtils.php delete mode 100644 application/frontend/config/.gitignore delete mode 100644 application/frontend/config/bootstrap.php delete mode 100644 application/frontend/config/main.php delete mode 100644 application/frontend/controllers/BuildAdminController.php delete mode 100644 application/frontend/controllers/ClientAdminController.php delete mode 100644 application/frontend/controllers/JobAdminController.php delete mode 100644 application/frontend/controllers/JobController.php delete mode 100644 application/frontend/controllers/OperationQueueAdminController.php delete mode 100644 application/frontend/controllers/ProjectAdminController.php delete mode 100644 application/frontend/controllers/ProjectController.php delete mode 100644 application/frontend/controllers/ReleaseAdminController.php delete mode 100644 application/frontend/controllers/SiteController.php delete mode 100644 application/frontend/controllers/SystemController.php delete mode 100644 application/frontend/models/ContactForm.php delete mode 100644 application/frontend/models/PasswordResetRequestForm.php delete mode 100644 application/frontend/models/ResetPasswordForm.php delete mode 100644 application/frontend/models/SignupForm.php delete mode 100644 application/frontend/runtime/.gitignore delete mode 100644 application/frontend/views/build-admin/_form.php delete mode 100644 application/frontend/views/build-admin/create.php delete mode 100644 application/frontend/views/build-admin/index.php delete mode 100644 application/frontend/views/build-admin/update.php delete mode 100644 application/frontend/views/build-admin/view.php delete mode 100644 application/frontend/views/client-admin/_form.php delete mode 100644 application/frontend/views/client-admin/create.php delete mode 100644 application/frontend/views/client-admin/index.php delete mode 100644 application/frontend/views/client-admin/update.php delete mode 100644 application/frontend/views/client-admin/view.php delete mode 100644 application/frontend/views/job-admin/_form.php delete mode 100644 application/frontend/views/job-admin/create.php delete mode 100644 application/frontend/views/job-admin/index.php delete mode 100644 application/frontend/views/job-admin/update.php delete mode 100644 application/frontend/views/job-admin/view.php delete mode 100644 application/frontend/views/layouts/main.php delete mode 100644 application/frontend/views/operation-queue-admin/_form.php delete mode 100644 application/frontend/views/operation-queue-admin/create.php delete mode 100644 application/frontend/views/operation-queue-admin/index.php delete mode 100644 application/frontend/views/operation-queue-admin/update.php delete mode 100644 application/frontend/views/operation-queue-admin/view.php delete mode 100644 application/frontend/views/project-admin/_form.php delete mode 100644 application/frontend/views/project-admin/create.php delete mode 100644 application/frontend/views/project-admin/index.php delete mode 100644 application/frontend/views/project-admin/update.php delete mode 100644 application/frontend/views/project-admin/view.php delete mode 100644 application/frontend/views/release-admin/_form.php delete mode 100644 application/frontend/views/release-admin/create.php delete mode 100644 application/frontend/views/release-admin/index.php delete mode 100644 application/frontend/views/release-admin/update.php delete mode 100644 application/frontend/views/release-admin/view.php delete mode 100644 application/frontend/views/site/about.php delete mode 100644 application/frontend/views/site/contact.php delete mode 100644 application/frontend/views/site/error.php delete mode 100644 application/frontend/views/site/index.php delete mode 100644 application/frontend/views/site/login.php delete mode 100644 application/frontend/views/site/requestPasswordResetToken.php delete mode 100644 application/frontend/views/site/resetPassword.php delete mode 100644 application/frontend/views/site/signup.php delete mode 100644 application/frontend/web/.gitignore delete mode 100644 application/frontend/web/.htaccess delete mode 100644 application/frontend/web/SILLogoBlue132x184.png delete mode 100644 application/frontend/web/assets/.gitignore delete mode 100644 application/frontend/web/css/site.css delete mode 100644 application/frontend/web/favicon.ico delete mode 100644 application/frontend/web/index-test.php delete mode 100644 application/frontend/web/index.php delete mode 100644 application/frontend/web/robots.txt delete mode 100644 application/frontend/widgets/Alert.php delete mode 100755 application/init delete mode 100644 application/init.bat delete mode 100644 application/quickstart.php delete mode 100755 application/rebuildbasemodels.sh delete mode 100644 application/requirements.php delete mode 100755 application/run-cron.sh delete mode 100755 application/run-tests.sh delete mode 100755 application/run.sh delete mode 100644 application/tests/README.md delete mode 100644 application/tests/_bootstrap.php delete mode 100644 application/tests/_data/dump.sql delete mode 100644 application/tests/_support/AcceptanceHelper.php delete mode 100644 application/tests/_support/AcceptanceTester.php delete mode 100644 application/tests/_support/FunctionalHelper.php delete mode 100644 application/tests/_support/FunctionalTester.php delete mode 100644 application/tests/_support/UnitHelper.php delete mode 100644 application/tests/_support/UnitTester.php delete mode 100644 application/tests/_support/_generated/AcceptanceTesterActions.php delete mode 100644 application/tests/_support/_generated/FunctionalTesterActions.php delete mode 100644 application/tests/_support/_generated/UnitTesterActions.php delete mode 100644 application/tests/acceptance.suite.yml delete mode 100644 application/tests/acceptance/AcceptanceTester.php delete mode 100644 application/tests/acceptance/_bootstrap.php delete mode 100644 application/tests/codeception.yml delete mode 100644 application/tests/codeception/_output/.gitignore delete mode 100644 application/tests/codeception/backend/.gitignore delete mode 100644 application/tests/codeception/backend/_bootstrap.php delete mode 100644 application/tests/codeception/backend/_output/.gitignore delete mode 100644 application/tests/codeception/backend/acceptance.suite.yml delete mode 100644 application/tests/codeception/backend/acceptance/LoginCept.php delete mode 100644 application/tests/codeception/backend/acceptance/_bootstrap.php delete mode 100644 application/tests/codeception/backend/codeception.yml delete mode 100644 application/tests/codeception/backend/functional.suite.yml delete mode 100644 application/tests/codeception/backend/functional/LoginCept.php delete mode 100644 application/tests/codeception/backend/functional/_bootstrap.php delete mode 100644 application/tests/codeception/backend/unit.suite.yml delete mode 100644 application/tests/codeception/backend/unit/DbTestCase.php delete mode 100644 application/tests/codeception/backend/unit/TestCase.php delete mode 100644 application/tests/codeception/backend/unit/_bootstrap.php delete mode 100644 application/tests/codeception/backend/unit/fixtures/data/.gitkeep delete mode 100644 application/tests/codeception/bin/_bootstrap.php delete mode 100644 application/tests/codeception/bin/yii delete mode 100644 application/tests/codeception/bin/yii.bat delete mode 100644 application/tests/codeception/common/.gitignore delete mode 100644 application/tests/codeception/common/_bootstrap.php delete mode 100644 application/tests/codeception/common/_output/.gitignore delete mode 100644 application/tests/codeception/common/_pages/LoginPage.php delete mode 100644 application/tests/codeception/common/_support/FixtureHelper.php delete mode 100644 application/tests/codeception/common/codeception.yml delete mode 100644 application/tests/codeception/common/fixtures/UserFixture.php delete mode 100644 application/tests/codeception/common/fixtures/data/init_login.php delete mode 100644 application/tests/codeception/common/templates/fixtures/user.php delete mode 100644 application/tests/codeception/common/unit.suite.yml delete mode 100644 application/tests/codeception/common/unit/DbTestCase.php delete mode 100644 application/tests/codeception/common/unit/TestCase.php delete mode 100644 application/tests/codeception/common/unit/_bootstrap.php delete mode 100644 application/tests/codeception/common/unit/fixtures/data/models/user.php delete mode 100644 application/tests/codeception/common/unit/models/LoginFormTest.php delete mode 100644 application/tests/codeception/config/acceptance.php delete mode 100644 application/tests/codeception/config/backend/acceptance.php delete mode 100644 application/tests/codeception/config/backend/config.php delete mode 100644 application/tests/codeception/config/backend/functional.php delete mode 100644 application/tests/codeception/config/backend/unit.php delete mode 100644 application/tests/codeception/config/common/unit.php delete mode 100644 application/tests/codeception/config/config.php delete mode 100644 application/tests/codeception/config/console/unit.php delete mode 100644 application/tests/codeception/config/frontend/acceptance.php delete mode 100644 application/tests/codeception/config/frontend/config.php delete mode 100644 application/tests/codeception/config/frontend/functional.php delete mode 100644 application/tests/codeception/config/frontend/unit.php delete mode 100644 application/tests/codeception/config/functional.php delete mode 100644 application/tests/codeception/config/unit.php delete mode 100644 application/tests/codeception/console/.gitignore delete mode 100644 application/tests/codeception/console/_bootstrap.php delete mode 100644 application/tests/codeception/console/_output/.gitignore delete mode 100644 application/tests/codeception/console/codeception.yml delete mode 100644 application/tests/codeception/console/unit.suite.yml delete mode 100644 application/tests/codeception/console/unit/DbTestCase.php delete mode 100644 application/tests/codeception/console/unit/TestCase.php delete mode 100644 application/tests/codeception/console/unit/_bootstrap.php delete mode 100644 application/tests/codeception/console/unit/fixtures/data/.gitkeep delete mode 100644 application/tests/codeception/frontend/.gitignore delete mode 100644 application/tests/codeception/frontend/_bootstrap.php delete mode 100644 application/tests/codeception/frontend/_output/.gitignore delete mode 100644 application/tests/codeception/frontend/_pages/AboutPage.php delete mode 100644 application/tests/codeception/frontend/_pages/ContactPage.php delete mode 100644 application/tests/codeception/frontend/_pages/SignupPage.php delete mode 100644 application/tests/codeception/frontend/acceptance.suite.yml delete mode 100644 application/tests/codeception/frontend/acceptance/AboutCept.php delete mode 100644 application/tests/codeception/frontend/acceptance/ContactCept.php delete mode 100644 application/tests/codeception/frontend/acceptance/HomeCept.php delete mode 100644 application/tests/codeception/frontend/acceptance/LoginCept.php delete mode 100644 application/tests/codeception/frontend/acceptance/SignupCest.php delete mode 100644 application/tests/codeception/frontend/acceptance/_bootstrap.php delete mode 100644 application/tests/codeception/frontend/codeception.yml delete mode 100644 application/tests/codeception/frontend/functional.suite.yml delete mode 100644 application/tests/codeception/frontend/functional/AboutCept.php delete mode 100644 application/tests/codeception/frontend/functional/ContactCept.php delete mode 100644 application/tests/codeception/frontend/functional/HomeCept.php delete mode 100644 application/tests/codeception/frontend/functional/LoginCept.php delete mode 100644 application/tests/codeception/frontend/functional/SignupCest.php delete mode 100644 application/tests/codeception/frontend/functional/_bootstrap.php delete mode 100644 application/tests/codeception/frontend/unit.suite.yml delete mode 100644 application/tests/codeception/frontend/unit/DbTestCase.php delete mode 100644 application/tests/codeception/frontend/unit/TestCase.php delete mode 100644 application/tests/codeception/frontend/unit/_bootstrap.php delete mode 100644 application/tests/codeception/frontend/unit/fixtures/data/models/user.php delete mode 100644 application/tests/codeception/frontend/unit/models/ContactFormTest.php delete mode 100644 application/tests/codeception/frontend/unit/models/PasswordResetRequestFormTest.php delete mode 100644 application/tests/codeception/frontend/unit/models/ResetPasswordFormTest.php delete mode 100644 application/tests/codeception/frontend/unit/models/SignupFormTest.php delete mode 100644 application/tests/functional.suite.yml delete mode 100644 application/tests/functional/FunctionalTester.php delete mode 100644 application/tests/functional/_bootstrap.php delete mode 100644 application/tests/mock/aws/codebuild/MockCodeBuildClient.php delete mode 100644 application/tests/mock/aws/codebuild/MockPromise.php delete mode 100644 application/tests/mock/aws/codecommit/MockCodeCommitClient.php delete mode 100644 application/tests/mock/aws/iam/MockIAmGroup.php delete mode 100644 application/tests/mock/aws/iam/MockIAmResult.php delete mode 100644 application/tests/mock/aws/iam/MockIAmUser.php delete mode 100644 application/tests/mock/aws/iam/MockIamClient.php delete mode 100644 application/tests/mock/aws/s3/MockS3Client.php delete mode 100644 application/tests/mock/aws/sts/MockStsClient.php delete mode 100644 application/tests/mock/common/components/MockAppbuilder_logger.php delete mode 100644 application/tests/mock/common/components/MockFileUtils.php delete mode 100644 application/tests/mock/common/components/MockIAmWrapper.php delete mode 100644 application/tests/mock/common/components/MockJenkinsUtils.php delete mode 100644 application/tests/mock/console/controllers/MockCronController.php delete mode 100644 application/tests/mock/gitWrapper/MockGitWorkingCopy.php delete mode 100644 application/tests/mock/gitWrapper/MockGitWrapper.php delete mode 100644 application/tests/mock/jenkins/MockJenkins.php delete mode 100644 application/tests/mock/jenkins/MockJenkinsBuild.php delete mode 100644 application/tests/mock/jenkins/MockJenkinsJob.php delete mode 100644 application/tests/mock/mailer/MockMailer.php delete mode 100644 application/tests/unit.suite.yml delete mode 100644 application/tests/unit/UnitTestBase.php delete mode 100644 application/tests/unit/UnitTester.php delete mode 100644 application/tests/unit/_bootstrap.php delete mode 100644 application/tests/unit/common/components/BuildTest.php delete mode 100644 application/tests/unit/common/components/CodeBuildTest.php delete mode 100644 application/tests/unit/common/components/EmailUtilsTest.php delete mode 100644 application/tests/unit/common/components/IAmWrapperTest.php delete mode 100644 application/tests/unit/common/components/ProjectTest.php delete mode 100644 application/tests/unit/common/components/S3Test.php delete mode 100644 application/tests/unit/common/components/STSTest.php delete mode 100644 application/tests/unit/console/components/CopyErrorToS3OperationTest.php delete mode 100644 application/tests/unit/console/components/CopyToS3OperationTest.php delete mode 100644 application/tests/unit/console/components/ManageBuildActionTest.php delete mode 100644 application/tests/unit/console/components/ManageProjectsActionTest.php delete mode 100644 application/tests/unit/console/components/ManageReleasesActionTest.php delete mode 100644 application/tests/unit/console/components/ProjectUpdateOperationTest.php delete mode 100644 application/tests/unit/console/components/RemoveExpiredBuildsActionTest.php delete mode 100644 application/tests/unit/console/components/S3MaintenanceActionTest.php delete mode 100644 application/tests/unit/fixtures/common/models/BuildFixture.php delete mode 100644 application/tests/unit/fixtures/common/models/EmailQueueFixture.php delete mode 100644 application/tests/unit/fixtures/common/models/JobFixture.php delete mode 100644 application/tests/unit/fixtures/common/models/OperationQueueFixture.php delete mode 100644 application/tests/unit/fixtures/common/models/ProjectFixture.php delete mode 100644 application/tests/unit/fixtures/common/models/ReleaseFixture.php delete mode 100644 application/tests/unit/fixtures/data/common/models/Build.php delete mode 100644 application/tests/unit/fixtures/data/common/models/EmailQueue.php delete mode 100644 application/tests/unit/fixtures/data/common/models/Job.php delete mode 100644 application/tests/unit/fixtures/data/common/models/OperationQueue.php delete mode 100644 application/tests/unit/fixtures/data/common/models/Project.php delete mode 100644 application/tests/unit/fixtures/data/common/models/Release.php delete mode 100644 application/tests/unit/frontend/components/JobControllerUtilsTest.php delete mode 100755 application/yii delete mode 100644 application/yii.bat delete mode 100644 build/appbuilder-cron delete mode 100644 build/appbuilder.conf delete mode 100644 build/logentries.all.crt delete mode 100644 build/rsyslog.conf delete mode 100644 build/sab delete mode 100644 common.env delete mode 100644 docker-compose.yml delete mode 100644 static/SILLogoBlue132x184.png diff --git a/.dockerignore b/.dockerignore index 8ca4438f..baf1aa6c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,2 @@ .git -phpinfo.php local.env -Vagrantfile diff --git a/.prettierignore b/.prettierignore index e537f8a3..e0d17d4b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -17,7 +17,3 @@ bun.lockb # Miscellaneous /static/ - -# Old -/application -/build \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index bfa7198e..00000000 --- a/Makefile +++ /dev/null @@ -1,55 +0,0 @@ -start: app - -test: clean testSh composer rmTestDb upTestDb yiimigratetestDb rmTestDb - docker compose run --rm cli bash -c 'MYSQL_HOST=testDb MYSQL_DATABASE=test ./vendor/bin/codecept --debug run unit' - -testSh: - shellcheck application/console/views/cron/scripts/upload/default/*.sh - -app: upDb composer yiimigrate adminer - docker compose up -d cron web - -composer: - docker compose run --rm --user="0:0" cli composer install - -composerupdate: - docker compose run --rm --user="0:0" cli composer update - -cli: - docker compose run --rm --user="0:0" cli bash - -rmDb: - docker compose kill db - docker compose rm -f db - -upDb: - docker compose up -d db - -yiimigrate: - docker compose run --rm cli whenavail db 3306 100 ./yii migrate --interactive=0 - -basemodels: - docker compose run --rm cli whenavail db 3306 100 ./rebuildbasemodels.sh - -yiimigratetestDb: - docker compose run --rm cli bash -c 'MYSQL_HOST=testDb MYSQL_DATABASE=test whenavail testDb 3306 100 ./yii migrate --interactive=0' - -rmTestDb: - docker compose kill testDb - docker compose rm -f testDb - -upTestDb: - docker compose up -d testDb - -bounce: - docker compose up -d cron web - -clean: - docker compose kill - docker compose rm -f - -cleanVolumes: - docker volume rm `docker volume ls -qf dangling=true` - -adminer: - docker compose up -d adminer diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 4e13ed1a..00000000 --- a/Vagrantfile +++ /dev/null @@ -1,141 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : -# Copyright (c) 2015 SIL International -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - - -# All Vagrant configuration is done below. The "2" in Vagrant.configure -# configures the configuration version (we support older styles for -# backwards compatibility). Please don't change it unless you know what -# you're doing. -Vagrant.configure(2) do |config| - required_plugins = %w( vagrant-vbguest vagrant-disksize ) - _retry = false - required_plugins.each do |plugin| - unless Vagrant.has_plugin? plugin - system "vagrant plugin install #{plugin}" - _retry=true - end - end - - if (_retry) - exec "vagrant " + ARGV.join(' ') - end - - - # The most common configuration options are documented and commented below. - # For a complete reference, please see the online documentation at - # https://docs.vagrantup.com. - - # Every Vagrant development environment requires a box. You can search for - # boxes at https://atlas.hashicorp.com/search. - config.vm.box = "ubuntu/xenial64" - config.disksize.size = "20GB" - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. - # config.vm.network "forwarded_port", guest: 80, host: 8080 - - - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network "private_network", ip: "192.168.33.10" - #config.vm.network "private_network", ip: "192.168.70.249", nic_type: "virtio" - - # These lines override a virtual NIC that the AlbanMontaigu/boot2docker box - # creates by default. If you need to change the the box's IP address (which - # is necessary to run separate, simulataneous instances of this Vagrantfile), - # do it here. - config.vm.provider "virtualbox" do |v, override| - # Enable gui for troubleshooting with boot - # v.gui = true - # Create a private network for accessing VM without NAT - override.vm.network "private_network", ip: "192.168.70.121" - end - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network "public_network" - - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - - # Synced folders for container data. - # Note: override mount_options on default sync folder to fix permissions - config.vm.synced_folder ".", "/vagrant", - # 33 is the www-data user/group in the ubuntu container - mount_options: ["uid=33","gid=33","fmode=755","dmode=755"] - - config.vm.provider "virtualbox" do |vb| - # Customize the amount of memory on the VM: database needs more memory - vb.memory = "2048" - - # A fix for speed issues with DNS resolution: - # http://serverfault.com/questions/453185/vagrant-virtualbox-dns-10-0-2-3-not-working?rq=1 - #vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] - vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"] - - # Set the timesync threshold to 59 seconds, instead of the default 20 minutes. - # 59 seconds chosen to ensure SimpleSAML never gets too far out of date. - vb.customize ["guestproperty", "set", :id, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold", 59000] - end - - - # This provisioner runs on the first `vagrant up`. - config.vm.provision "install", type: "shell", inline: <<-SHELL - # Add Docker apt repository - sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D - sudo sh -c 'echo deb https://apt.dockerproject.org/repo ubuntu-xenial main > /etc/apt/sources.list.d/docker.list' - sudo apt-get update -y - # Uninstall old lxc-docker - apt-get purge lxc-docker - apt-cache policy docker-engine - # Install docker and dependencies - sudo apt-get install -y linux-image-extra-$(uname -r) - sudo apt-get install -y docker-engine - # Add user vagrant to docker group - sudo groupadd docker - sudo usermod -aG docker vagrant - # Install Docker Compose - curl -sS -L https://github.com/docker/compose/releases/download/1.5.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose - - echo 'export DOCKER_UIDGID=0:0' >> /home/vagrant/.profile - echo 'cd /vagrant' >> /home/vagrant/.profile - SHELL - - - # This provisioner runs on every `vagrant reload' (as well as the first - # `vagrant up`), reinstalling from local directories - config.vm.provision "recompose", type: "shell", - run: "always", inline: <<-SHELL - # Run docker-compose (which will update preloaded images, and - # pulls any images not preloaded) - cd /vagrant - - # Start services - DOCKER_UIDGID="0:0" make - SHELL -end diff --git a/application/.bowerrc b/application/.bowerrc deleted file mode 100644 index 1669168f..00000000 --- a/application/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory" : "vendor/bower" -} diff --git a/application/.gitignore b/application/.gitignore deleted file mode 100644 index 72d1229a..00000000 --- a/application/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ - -# netbeans project files -nbproject - -# zend studio for eclipse project files -.buildpath -.project -.settings - -# windows thumbnail cache -Thumbs.db - -# composer vendor dir -vendor/ - -# composer itself is not needed -composer.phar - -# Mac DS_Store Files -.DS_Store - -# phpunit itself is not needed -phpunit.phar -# local phpunit config -/phpunit.xml - -# ssh keys for git -.ssh/ - -tests/_output/* -tests/_output/* \ No newline at end of file diff --git a/application/LICENSE.md b/application/LICENSE.md deleted file mode 100644 index e98f03df..00000000 --- a/application/LICENSE.md +++ /dev/null @@ -1,32 +0,0 @@ -The Yii framework is free software. It is released under the terms of -the following BSD License. - -Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name of Yii Software LLC nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/application/README.md b/application/README.md deleted file mode 100644 index 549e3782..00000000 --- a/application/README.md +++ /dev/null @@ -1,97 +0,0 @@ -Yii 2 Advanced Application Template -=================================== - -Yii 2 Advanced Application Template is a skeleton Yii 2 application best for -developing complex Web applications with multiple tiers. - -The template includes three tiers: front end, back end, and console, each of which -is a separate Yii application. - -The template is designed to work in a team development environment. It supports -deploying the application in different environments. - - -DIRECTORY STRUCTURE -------------------- - -``` -common - config/ contains shared configurations - mail/ contains view files for e-mails - models/ contains model classes used in both backend and frontend -console - config/ contains console configurations - controllers/ contains console controllers (commands) - migrations/ contains database migrations - models/ contains console-specific model classes - runtime/ contains files generated during runtime -backend - assets/ contains application assets such as JavaScript and CSS - config/ contains backend configurations - controllers/ contains Web controller classes - models/ contains backend-specific model classes - runtime/ contains files generated during runtime - views/ contains view files for the Web application - web/ contains the entry script and Web resources -frontend - assets/ contains application assets such as JavaScript and CSS - config/ contains frontend configurations - controllers/ contains Web controller classes - models/ contains frontend-specific model classes - runtime/ contains files generated during runtime - views/ contains view files for the Web application - web/ contains the entry script and Web resources - widgets/ contains frontend widgets -vendor/ contains dependent 3rd-party packages -environments/ contains environment-based overrides -tests contains various tests for the advanced application - codeception/ contains tests developed with Codeception PHP Testing Framework -``` - - -REQUIREMENTS ------------- - -The minimum requirement by this application template that your Web server supports PHP 5.4.0. - - -INSTALLATION ------------- - -### Install from an Archive File - -Extract the archive file downloaded from [yiiframework.com](http://www.yiiframework.com/download/) to -a directory named `advanced` that is directly under the Web root. - -Then follow the instructions given in "GETTING STARTED". - - -### Install via Composer - -If you do not have [Composer](http://getcomposer.org/), you may install it by following the instructions -at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix). - -You can then install the application using the following command: - -~~~ -php composer.phar global require "fxp/composer-asset-plugin:1.0.0-beta4" -php composer.phar create-project --prefer-dist --stability=dev yiisoft/yii2-app-advanced advanced -~~~ - - -GETTING STARTED ---------------- - -After you install the application, you have to conduct the following steps to initialize -the installed application. You only need to do these once for all. - -1. Run command `init` to initialize the application with a specific environment. -2. Create a new database and adjust the `components['db']` configuration in `common/config/main-local.php` accordingly. -3. Apply migrations with console command `yii migrate`. This will create tables needed for the application to work. -4. Set document roots of your Web server: - -- for frontend `/path/to/yii-application/frontend/web/` and using the URL `http://frontend/` -- for backend `/path/to/yii-application/backend/web/` and using the URL `http://backend/` - -To login into the application, you need to first sign up, with any of your email address, username and password. -Then, you can login into the application with same email address and password at any time. diff --git a/application/backend/assets/AppAsset.php b/application/backend/assets/AppAsset.php deleted file mode 100644 index c2621429..00000000 --- a/application/backend/assets/AppAsset.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class AppAsset extends AssetBundle -{ - public $basePath = '@webroot'; - public $baseUrl = '@web'; - public $css = [ - 'css/site.css', - ]; - public $js = [ - ]; - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/application/backend/config/.gitignore b/application/backend/config/.gitignore deleted file mode 100644 index 20da318c..00000000 --- a/application/backend/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/application/backend/config/bootstrap.php b/application/backend/config/bootstrap.php deleted file mode 100644 index b3d9bbc7..00000000 --- a/application/backend/config/bootstrap.php +++ /dev/null @@ -1 +0,0 @@ - 'app-backend', - 'basePath' => dirname(__DIR__), - 'controllerNamespace' => 'backend\controllers', - 'bootstrap' => ['log'], - 'modules' => [], - 'components' => [ - 'user' => [ - 'identityClass' => 'common\models\User', - 'enableAutoLogin' => true, - ], - 'log' => [ - 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - 'errorHandler' => [ - 'errorAction' => 'site/error', - ], - ], - 'params' => $params, -]; diff --git a/application/backend/config/params.php b/application/backend/config/params.php deleted file mode 100644 index 7f754b91..00000000 --- a/application/backend/config/params.php +++ /dev/null @@ -1,4 +0,0 @@ - 'admin@example.com', -]; diff --git a/application/backend/controllers/SiteController.php b/application/backend/controllers/SiteController.php deleted file mode 100644 index db3259a7..00000000 --- a/application/backend/controllers/SiteController.php +++ /dev/null @@ -1,83 +0,0 @@ - [ - 'class' => AccessControl::className(), - 'rules' => [ - [ - 'actions' => ['login', 'error'], - 'allow' => true, - ], - [ - 'actions' => ['logout', 'index'], - 'allow' => true, - 'roles' => ['@'], - ], - ], - ], - 'verbs' => [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'logout' => ['post'], - ], - ], - ]; - } - - /** - * @inheritdoc - */ - public function actions() - { - return [ - 'error' => [ - 'class' => 'yii\web\ErrorAction', - ], - ]; - } - - public function actionIndex() - { - return $this->render('index'); - } - - public function actionLogin() - { - if (!\Yii::$app->user->isGuest) { - return $this->goHome(); - } - - $model = new LoginForm(); - if ($model->load(Yii::$app->request->post()) && $model->login()) { - return $this->goBack(); - } else { - return $this->render('login', [ - 'model' => $model, - ]); - } - } - - public function actionLogout() - { - Yii::$app->user->logout(); - - return $this->goHome(); - } -} diff --git a/application/backend/models/.gitkeep b/application/backend/models/.gitkeep deleted file mode 100644 index 72e8ffc0..00000000 --- a/application/backend/models/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/application/backend/runtime/.gitignore b/application/backend/runtime/.gitignore deleted file mode 100644 index c96a04f0..00000000 --- a/application/backend/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/application/backend/views/layouts/main.php b/application/backend/views/layouts/main.php deleted file mode 100644 index e3f294fa..00000000 --- a/application/backend/views/layouts/main.php +++ /dev/null @@ -1,71 +0,0 @@ - -beginPage() ?> - - - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - beginBody() ?> -
    - 'My Company', - 'brandUrl' => Yii::$app->homeUrl, - 'options' => [ - 'class' => 'navbar-inverse navbar-fixed-top', - ], - ]); - $menuItems = [ - ['label' => 'Home', 'url' => ['/site/index']], - ]; - if (Yii::$app->user->isGuest) { - $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; - } else { - $menuItems[] = [ - 'label' => 'Logout (' . Yii::$app->user->identity->username . ')', - 'url' => ['/site/logout'], - 'linkOptions' => ['data-method' => 'post'] - ]; - } - echo Nav::widget([ - 'options' => ['class' => 'navbar-nav navbar-right'], - 'items' => $menuItems, - ]); - NavBar::end(); - ?> - -
    - isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], - ]) ?> - -
    -
    - -
    -
    -

    © My Company

    -

    -
    -
    - - endBody() ?> - - -endPage() ?> diff --git a/application/backend/views/site/error.php b/application/backend/views/site/error.php deleted file mode 100644 index b9812c48..00000000 --- a/application/backend/views/site/error.php +++ /dev/null @@ -1,27 +0,0 @@ -title = $name; -?> -
    - -

    title) ?>

    - -
    - -
    - -

    - The above error occurred while the Web server was processing your request. -

    -

    - Please contact us if you think this is a server error. Thank you. -

    - -
    diff --git a/application/backend/views/site/index.php b/application/backend/views/site/index.php deleted file mode 100644 index 0159befe..00000000 --- a/application/backend/views/site/index.php +++ /dev/null @@ -1,52 +0,0 @@ -title = 'My Yii Application'; -?> -
    - -
    -

    Congratulations!

    - -

    You have successfully created your Yii-powered application.

    - -

    Get started with Yii

    -
    - -
    - -
    -
    -

    Heading

    - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

    - -

    Yii Documentation »

    -
    -
    -

    Heading

    - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

    - -

    Yii Forum »

    -
    -
    -

    Heading

    - -

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip - ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur.

    - -

    Yii Extensions »

    -
    -
    - -
    -
    diff --git a/application/backend/views/site/login.php b/application/backend/views/site/login.php deleted file mode 100644 index a4041a96..00000000 --- a/application/backend/views/site/login.php +++ /dev/null @@ -1,29 +0,0 @@ -title = 'Login'; -$this->params['breadcrumbs'][] = $this->title; -?> - diff --git a/application/backend/web/.gitignore b/application/backend/web/.gitignore deleted file mode 100644 index 8b137891..00000000 --- a/application/backend/web/.gitignore +++ /dev/null @@ -1 +0,0 @@ - diff --git a/application/backend/web/assets/.gitignore b/application/backend/web/assets/.gitignore deleted file mode 100644 index d6b7ef32..00000000 --- a/application/backend/web/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/application/backend/web/css/site.css b/application/backend/web/css/site.css deleted file mode 100644 index 698be709..00000000 --- a/application/backend/web/css/site.css +++ /dev/null @@ -1,91 +0,0 @@ -html, -body { - height: 100%; -} - -.wrap { - min-height: 100%; - height: auto; - margin: 0 auto -60px; - padding: 0 0 60px; -} - -.wrap > .container { - padding: 70px 15px 20px; -} - -.footer { - height: 60px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - padding-top: 20px; -} - -.jumbotron { - text-align: center; - background-color: transparent; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -.not-set { - color: #c55; - font-style: italic; -} - -/* add sorting icons to gridview sort links */ -a.asc:after, a.desc:after { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - padding-left: 5px; -} - -a.asc:after { - content: /*"\e113"*/ "\e151"; -} - -a.desc:after { - content: /*"\e114"*/ "\e152"; -} - -.sort-numerical a.asc:after { - content: "\e153"; -} - -.sort-numerical a.desc:after { - content: "\e154"; -} - -.sort-ordinal a.asc:after { - content: "\e155"; -} - -.sort-ordinal a.desc:after { - content: "\e156"; -} - -.grid-view th { - white-space: nowrap; -} - -.hint-block { - display: block; - margin-top: 5px; - color: #999; -} - -.error-summary { - color: #a94442; - background: #fdf7f7; - border-left: 3px solid #eed3d7; - padding: 10px 20px; - margin: 0 0 15px 0; -} diff --git a/application/backend/web/favicon.ico b/application/backend/web/favicon.ico deleted file mode 100644 index 580ed732e86556ec57f3f3395a210246d679c076..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|4HXaJKC0wf0lAEr2iX{M9K3=BR0 y!E90pK{x=K$Oz&POT#sS8N$ZKhC)h8ip0_|-T#43{vnSYgXBQCu@O54$pHYIza?e> diff --git a/application/backend/web/index-test.php b/application/backend/web/index-test.php deleted file mode 100644 index 8b967072..00000000 --- a/application/backend/web/index-test.php +++ /dev/null @@ -1,19 +0,0 @@ -run(); diff --git a/application/backend/web/index.php b/application/backend/web/index.php deleted file mode 100644 index 60381674..00000000 --- a/application/backend/web/index.php +++ /dev/null @@ -1,18 +0,0 @@ -run(); diff --git a/application/backend/web/robots.txt b/application/backend/web/robots.txt deleted file mode 100644 index c6742d8a..00000000 --- a/application/backend/web/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-Agent: * -Disallow: / diff --git a/application/codeception.yml b/application/codeception.yml deleted file mode 100644 index 306e7396..00000000 --- a/application/codeception.yml +++ /dev/null @@ -1,17 +0,0 @@ -actor: Tester -paths: - tests: tests - log: tests/_output - data: tests/_data - helpers: tests/_support -settings: - bootstrap: _bootstrap.php - colors: true - memory_limit: 1024M -modules: - config: - Db: - dsn: '' - user: '' - password: '' - dump: tests/_data/dump.sql diff --git a/application/common/components/AWSCommon.php b/application/common/components/AWSCommon.php deleted file mode 100644 index 0b4466ca..00000000 --- a/application/common/components/AWSCommon.php +++ /dev/null @@ -1,83 +0,0 @@ -params['buildEngineArtifactsBucketRegion']; - } - - public static function getArtifactsBucket() - { - return \Yii::$app->params['buildEngineArtifactsBucket']; - } - - public static function getAWSUserAccount() - { - return \YII::$app->params['awsUserId']; - } - - public static function getAppEnv() - { - return \Yii::$app->params['appEnv']; - } - - public static function getSecretsBucket() - { - return \Yii::$app->params['buildEngineSecretsBucket']; - } - - public static function getProjectsBucket() - { - return \Yii::$app->params['buildEngineProjectsBucket']; - } - - public static function getCodeBuildImageTag() - { - return \Yii::$app->params['codeBuildImageTag']; - } - public static function getCodeBuildImageRepo() - { - return \Yii::$app->params['codeBuildImageRepo']; - } - public static function getScriptureEarthKey() - { - return \Yii::$app->params['scriptureEarthKey']; - } - - public static function getBuildScriptPath() - { - $s3path = "s3://". AWSCommon::getProjectsBucket() .'/default'; - - return $s3path; - - } - public static function getArtifactPath($job, $productionStage, $isPublish = false) - { - $buildProcess = $job->nameForBuildProcess(); - if ($isPublish == true) - { - $buildProcess = $job->nameForPublishProcess(); - } - $jobNumber = (string)$job->id; - $artifactPath = $productionStage . '/jobs/' . $buildProcess . '_' . $jobNumber; - return $artifactPath; - } - /** - * Get the project name which is the prd or stg plus build_app or publish_app - * - * @param string $baseName build_app or publish_app - * @return string app name - */ - public static function getCodeBuildProjectName($baseName) - { - return ($baseName . "-" . self::getAppEnv()); - } - - public static function getRoleName($baseName) - { - return 'codebuild-' . $baseName . '-service-role-' . self::getAppEnv(); - } -} \ No newline at end of file diff --git a/application/common/components/Appbuilder_logger.php b/application/common/components/Appbuilder_logger.php deleted file mode 100644 index 499497dd..00000000 --- a/application/common/components/Appbuilder_logger.php +++ /dev/null @@ -1,133 +0,0 @@ -area = $area; - } - - function echo_stackTrace(\Exception $e){ - $trace = explode("\n", $e->getTraceAsString()); - $length = count($trace); - for ($i = 0; $i < $length; $i++) - { - echo "$trace[$i]" . PHP_EOL; - } - } - - function get_stackTraceDetails($e){ - $trace = $e->getTrace(); - $traceDetails = [ - 'stackTrace' => 'first lines of trace', - 'stackTrace0' => $trace[0], - 'stackTrace1' => $trace[1], - 'stackTrace2' => $trace[2], - 'stackTrace3' => $trace[3], - 'stackTrace4' => $trace[4] - ]; - return $traceDetails; - } - - /** - * Returns the calling function through a backtrace - */ - function get_calling_function() { - // a funciton x has called a function y which called this - // see stackoverflow.com/questions/190421 - $backtrace = debug_backtrace(); - $caller = $backtrace[3]; - $this->functionName = $caller['function']; - $r = $this->functionName . '()'; - if (isset($caller['class'])) { - $r .= ' in ' . $caller['class']; - } - if (isset($caller['object'])) { - $r .= ' (' . get_class($caller['object']) . ')'; - } - return $r; - } - - private static function getPrefix() - { - return date('Y-m-d H:i:s'); - } - - - /** - * - * Creates an error log to be submitted to logentries.com - */ - public function appbuilderErrorLog($log) - { - $this->outputToLogger($log, Logger::LEVEL_ERROR, "ERROR-"); - } - - /** - * - * Creates an error log to be submitted to logentries.com - * - * @param array $log - * @param \Exception $e - */ - public function appbuilderExceptionLog($log, $e) - { - $this->echo_stackTrace($e); - $logExceptionDetails = $this->get_stackTraceDetails($e); - $logExceptionDetails['LINE NUMBER:'] = $e->getLine(); - $logExceptionDetails['MESSAGE:'] = $e->getMessage(); - $mergedLog = array_merge($logExceptionDetails, $log); - $this->outputToLogger($mergedLog, Logger::LEVEL_ERROR, "EXCEPTION-"); - } - - /** - * - * Creates an warning log to be submitted to logentries.com - */ - public function appbuilderWarningLog($log) - { - $this->outputToLogger($log, Logger::LEVEL_WARNING, "WARNING-"); - } - - /** - * - * Creates an info log to be submitted to logentries.com - */ - public function appbuilderInfoLog($log) - { - //NOTE: rsyslog is not configured to log INFO level to avoid to many logs - $this->outputToLogger($log, Logger::LEVEL_WARNING, "INFO-"); - } - /** - * - * Creates a log to be submitted to logentries.com - */ - public function outputToLogger($log, $level, $logtype) - { - $prefix = self::getPrefix(); - $callingFunction = $this->get_calling_function(); - echo PHP_EOL . " callingFunction is:" .PHP_EOL ." $callingFunction" . PHP_EOL . PHP_EOL; - $logPrefix = [ - 'date' => $prefix, - 'functionAndClass' => $callingFunction - ]; - $category = "$logtype" . "$this->area" . '-' . "$this->functionName"; - $mergedLog = array_merge($logPrefix, $log); - \Yii::getLogger()->log($mergedLog, $level, $category); - } -} diff --git a/application/common/components/CodeBuild.php b/application/common/components/CodeBuild.php deleted file mode 100644 index 499c3b2e..00000000 --- a/application/common/components/CodeBuild.php +++ /dev/null @@ -1,544 +0,0 @@ -codeBuildClient = \Yii::$container->get('codeBuildClient'); - } catch (\Exception $e) { - // Get real S3 client - $this->codeBuildClient = self::getCodeBuildClient(); - } - $this->fileUtil = \Yii::$container->get('fileUtils'); - } - - /** - * Configure and get the CodeBuild Client - * @return \Aws\CodeBuild\CodeBuildClient - */ - public static function getCodeBuildClient() { - $client = new \Aws\CodeBuild\CodeBuildClient([ - 'region' => CodeBuild::getArtifactsBucketRegion(), - 'version' => '2016-10-06' - ]); - return $client; - } - - /** - * Start a build for the function - * - * @param string $repoUrl - * @param string $commitId - * @param string $build - * @param string $buildSpec Buildspec script to be executed - * @param string $versionCode - * @return string Guid part of build ID - */ - public function startBuild($repoUrl, $commitId, $build, $buildSpec, $versionCode, $codeCommit) { - $prefix = Utils::getPrefix(); - $job = $build->job; - $buildProcess = $job->nameForBuildProcess(); - $jobNumber = (string)$job->id; - $buildNumber = (string)$build->id; - echo "[$prefix] startBuild CodeBuild Project: " . $buildProcess . " URL: " .$repoUrl . " commitId: " . $commitId . " jobNumber: " . $jobNumber . " buildNumber: " . $buildNumber . " versionCode: " . $versionCode . PHP_EOL; - $artifacts_bucket = self::getArtifactsBucket(); - $secretsBucket = self::getSecretsBucket(); - $buildApp = self::getCodeBuildProjectName('build_app'); - $buildPath = $this->getBuildPath($job); - $artifactPath = $this->getArtifactPath($job, 'codebuild-output'); - echo "Artifacts path: " . $artifactPath . PHP_EOL; - // Leaving all this code together to make it easier to remove when git is no longer supported - if ($codeCommit) { - echo "[$prefix] startBuild CodeCommit Project"; - $promise = $this->codeBuildClient->startBuildAsync([ - 'projectName' => $buildApp, - 'artifactsOverride' => [ - 'location' => $artifacts_bucket, // output bucket - 'name' => '/', // name of output artifact object - 'namespaceType' => 'NONE', - 'packaging' => 'NONE', - 'path' => $artifactPath, // path to output artifacts - 'type' => 'S3', // REQUIRED - ], - 'buildspecOverride' => $buildSpec, - 'environmentVariablesOverride' => [ - [ - 'name' => 'BUILD_NUMBER', - 'value' => $buildNumber, - ], - [ - 'name' => 'APP_BUILDER_SCRIPT_PATH', - 'value' => $buildPath, - ], - [ - 'name' => 'PUBLISHER', - 'value' => $job->publisher_id, - ], - [ - 'name' => 'VERSION_CODE', - 'value' => $versionCode, - ], - [ - 'name' => 'SECRETS_BUCKET', - 'value' => $secretsBucket, - ] - ], - 'sourceLocationOverride' => $repoUrl, - 'sourceVersion' => $commitId - ]); - $state = $promise->getState(); - echo "state: " . $state . PHP_EOL; - $result = $promise->wait(true); - $buildInfo = $result['build']; - $buildId = $buildInfo['id']; - $buildGuid = substr($buildId, strrpos($buildId, ':') + 1); - echo "Build id: " . $buildId . " Guid: " . $buildGuid . PHP_EOL; - return $buildGuid; - } else { - echo "[$prefix] startBuild S3 Project" . PHP_EOL; - $targets = $build->targets; - if (is_null($targets)) { - $targets = 'apk play-listing'; - } - $environmentArray = [ - // BUILD_NUMBER Must be first for tests - [ - 'name' => 'BUILD_NUMBER', - 'value' => $buildNumber, - ], - [ - 'name' => 'APP_BUILDER_SCRIPT_PATH', - 'value' => $buildPath, - ], - [ - 'name' => 'PUBLISHER', - 'value' => $job->publisher_id, - ], - [ - 'name' => 'VERSION_CODE', - 'value' => $versionCode, - ], - [ - 'name' => 'SECRETS_BUCKET', - 'value' => $secretsBucket, - ], - [ - 'name' => 'PROJECT_S3', - 'value' => $repoUrl, - ], - [ - 'name' => 'TARGETS', - 'value' => $targets, - ], - [ - 'name' => 'SCRIPT_S3', - 'value' => S3::getBuildScriptPath(), - ] - ]; - $adjustedEnvironmentArray = $this->addEnvironmentToArray($environmentArray, $build->environment); - $computeType = $this->getComputeType($adjustedEnvironmentArray); - $imageTag = $this->getImageTag($adjustedEnvironmentArray); - $promise = $this->codeBuildClient->startBuildAsync([ - 'projectName' => $buildApp, - 'artifactsOverride' => [ - 'location' => $artifacts_bucket, // output bucket - 'name' => '/', // name of output artifact object - 'namespaceType' => 'NONE', - 'packaging' => 'NONE', - 'path' => $artifactPath, // path to output artifacts - 'type' => 'S3', // REQUIRED - ], - 'buildspecOverride' => $buildSpec, - 'environmentVariablesOverride' => $adjustedEnvironmentArray, - 'sourceTypeOverride' => 'NO_SOURCE', - 'computeTypeOverride' => $computeType, - 'imageOverride' => self::getCodeBuildImageRepo() . ":" . $imageTag, - - ]); - $state = $promise->getState(); - echo "state: " . $state . PHP_EOL; - $result = $promise->wait(true); - $buildInfo = $result['build']; - $buildId = $buildInfo['id']; - $buildGuid = substr($buildId, strrpos($buildId, ':') + 1); - echo "Build id: " . $buildId . " Guid: " . $buildGuid . PHP_EOL; - return $buildGuid; - } - } - - /** - * This method returns the build status object - * - * @param string $guid - Code Build GUID for the build - * @param string $buildProcess - Name of code build project (e.g. build_scriptureappbuilder) - * @return AWS/Result Result object on the status of the build - */ - public function getBuildStatus($guid, $buildProcess) { - $prefix = Utils::getPrefix(); - echo "[$prefix] getBuildStatus CodeBuild Project: " . $buildProcess . " BuildGuid: " . $guid . PHP_EOL; - - $buildId = $this->getBuildId($guid, $buildProcess); - $result = $this->codeBuildClient->batchGetBuilds([ - 'ids' => [ - $buildId - ] - ]); - $builds = $result['builds']; - try { - $statusOfSelectedBuild = $builds[0]; -// var_dump($statusOfSelectedBuild); - - return $statusOfSelectedBuild; - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] getBuildStatus: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - var_dump($result); - } - } - - /** - * This method returns the completion status of the job - * based upon the build status object passed in - */ - public function isBuildComplete($buildStatus) { - $complete = $buildStatus['buildComplete']; - return $complete; - } - /** - * This method returns the status of the build - * @param AWS/Result Return value from getBuildStatus - */ - public function getStatus($buildStatus) { - $status = $buildStatus['buildStatus']; - return $status; - } - - /** - * Recreate the build id - * - * @param string $guid Build GUID - * @param string $buildProcess CodeBuild Project Name (e.g. scriptureappbuilder) - * @return string CodeBuild build arn - */ - private function getBuildId($guid, $buildProcess) { - $buildId = $buildProcess . ':' . $guid; - echo "getBuildId arn: " . $buildId . PHP_EOL; - return $buildId; - } - /** - * Returns the name of the shell command to be run - * - * @param $job Job associated with this build - * @return string Name of the task to be run - */ - private function getBuildPath($job) { - $app_id = $job->app_id; - $retVal = 'unknown'; - switch ($app_id) { - case Job::APP_TYPE_SCRIPTUREAPP: - $retVal = 'scripture-app-builder'; - break; - case Job::APP_TYPE_READINGAPP: - $retVal = 'reading-app-builder'; - break; - case Job::APP_TYPE_DICTIONARYAPP: - $retVal = 'dictionary-app-builder'; - break; - case Job::APP_TYPE_KEYBOARDAPP: - $retVal = 'keyboard-app-builder'; - break; - default: - $retVal = 'unknown'; - break; - } - return $retVal; - } - - /** - * Starts a publish action - * @param Release $release - * @param string $releaseSpec - * @return false|string - */ - public function startRelease($release, $releaseSpec) - { - echo 'startRelease: ' . PHP_EOL; - $prefix = Utils::getPrefix(); - $releaseNumber = (string)$release->id; - $build = $release->build; - $buildNumber = (string)$build->id; - $job = $build->job; - $artifactUrl = $build->apk(); - $buildPath = $this->getBuildPath($job); - $artifacts_bucket = self::getArtifactsBucket(); - $artifactPath = $this->getArtifactPath($job, 'codebuild-output', true); - $secretsBucket = self::getSecretsBucket(); - $scriptureEarthKey = self::getScriptureEarthKey(); - $publishApp = self::getCodeBuildProjectName('publish_app'); - $promoteFrom = $release->promote_from; - if (is_null($promoteFrom)) { - $promoteFrom = ""; - } - - $sourceLocation = $this->getSourceLocation($build); - $s3Artifacts = $this->getArtifactsLocation($build); - echo 'Source location: ' . $sourceLocation . PHP_EOL; - $targets = $release->targets; - if (is_null($targets)) { - $targets = 'google-play'; - } - - $environmentArray = [ - // RELEASE_NUMBER Must be first for tests - [ - 'name' => 'RELEASE_NUMBER', - 'value' => $releaseNumber, - ], - [ - 'name' => 'APP_BUILDER_SCRIPT_PATH', - 'value' => $buildPath, - ], - [ - 'name' => 'BUILD_NUMBER', - 'value' => $buildNumber, - ], - [ - 'name' => 'CHANNEL', - 'value' => $release->channel, - ], - [ - 'name' => 'PUBLISHER', - 'value' => $job->publisher_id, - ], - [ - 'name' => 'PROJECT_S3', - 'value' => $job->git_url, - ], - [ - 'name' => 'SECRETS_BUCKET', - 'value' => $secretsBucket, - ], - [ - 'name' => 'PROMOTE_FROM', - 'value' => $promoteFrom, - ], - [ - 'name' => 'ARTIFACTS_S3_DIR', - 'value' => $s3Artifacts, - ], - [ - 'name' => 'TARGETS', - 'value' => $targets, - ], - [ - 'name' => 'SCRIPT_S3', - 'value' => S3::getBuildScriptPath(), - ], - [ - 'name' => 'SCRIPTURE_EARTH_KEY', - 'value' => $scriptureEarthKey, - ] - ]; - $adjustedEnvironmentArray = $this->addEnvironmentToArray($environmentArray, $release->environment); - $promise = $this->codeBuildClient->startBuildAsync([ - 'projectName' => $publishApp, - 'artifactsOverride' => [ - 'location' => $artifacts_bucket, // output bucket - 'name' => '/', // name of output artifact object - 'namespaceType' => 'NONE', - 'packaging' => 'NONE', - 'path' => $artifactPath, // path to output artifacts - 'type' => 'S3', // REQUIRED - ], - 'buildspecOverride' => $releaseSpec, - 'environmentVariablesOverride' => $adjustedEnvironmentArray, - 'sourceLocationOverride' => $sourceLocation, - ]); - $state = $promise->getState(); - echo "state: " . $state . PHP_EOL; - $result = $promise->wait(true); - $buildInfo = $result['build']; - $buildId = $buildInfo['id']; - $buildGuid = substr($buildId, strrpos($buildId, ':') + 1); - echo "Build id: " . $buildId . " Guid: " . $buildGuid . PHP_EOL; - return $buildGuid; - } - /** - * Get the url for the apk file in a format that codebuild accepts for an S3 Source - * We are using the apk file as a source, even though we're not really using it because - * codebuild requires a source and if S3 is the type, it must be a zip file. - * - * @param Build $build - build object for this operation - * @return string - Arn format for the apk file - */ - private function getSourceLocation($build) - { - $appEnv = S3::getAppEnv(); - $apkFilename = $build->apkFilename(); - $sourceLocation = S3::getS3Arn($build, $appEnv, $apkFilename); - return $sourceLocation; - } - /** - * Get the URL for the S3 artifacts folder in the format required by the buildspec - * - * @param Build $build - build object for this operation - * @return string - s3:// url format for s3 artifacts folder - */ - private function getArtifactsLocation($build) - { - $artifactsBucket = self::getArtifactsBucket(); - $artifactFolder = $build->getBasePrefixUrl(self::getAppEnv()); - $artifactsLocation = 's3://' . $artifactsBucket . '/' . $artifactFolder; - return($artifactsLocation); - } - - /** - * This function creates a project in CodeBuild - * - * @param string $base_name base project being built, e.g. build_app or publish_app - * @param string $role_arn Arn for the IAm role - * @param Array $cache Strings defining the cache parameter of the build - * @param Array $source Strings defining the source parameter of the build - * - */ - public function createProject($base_name, $role_arn, $cache, $source) { - $project_name = self::getCodeBuildProjectName($base_name); - $artifacts_bucket = self::getArtifactsBucket(); - echo "Bucket: $artifacts_bucket" . PHP_EOL; - $result = $this->codeBuildClient->createProject([ - 'artifacts' => [ // REQUIRED - 'location' => $artifacts_bucket, // output bucket - 'name' => '/', // name of output artifact object - 'namespaceType' => 'NONE', - 'packaging' => 'NONE', - 'path' => 'codebuild-output', // path to output artifacts - 'type' => 'S3', // REQUIRED - ], - 'cache' => $cache, - 'environment' => [ // REQUIRED - 'computeType' => 'BUILD_GENERAL1_SMALL', // REQUIRED - 'image' => self::getCodeBuildImageRepo() . ":" . self::getCodeBuildImageTag(), // REQUIRED - 'privilegedMode' => false, - 'type' => 'LINUX_CONTAINER', // REQUIRED - ], - 'name' => $project_name, // REQUIRED - 'serviceRole' => $role_arn, - 'source' => $source, - ]); - } - - public static function getConsoleTextUrl($baseName, $guid) - { - $projectName = self::getCodeBuildProjectName($baseName); - $region = getenv('BUILD_ENGINE_ARTIFACTS_BUCKET_REGION') ?: "us-east-1"; - $regionUrl = 'https://console.aws.amazon.com/cloudwatch/home?region=' . $region; - $taskExtension = '#logEvent:group=/aws/codebuild/' . $projectName . ';stream=' . $guid; - return $regionUrl . $taskExtension; - - } - public static function getCodeBuildUrl($baseName, $guid) - { - $projectName = self::getCodeBuildProjectName($baseName); - $region = getenv('BUILD_ENGINE_ARTIFACTS_BUCKET_REGION') ?: "us-east-1"; - $regionUrl = 'https://console.aws.amazon.com/codebuild/home?region=' . $region; - $taskExtension = '#/builds/' . $projectName . ':' . $guid . '/view/new'; - return $regionUrl . $taskExtension; - - } - /** - * Checks to see if the current project exists - * - * @param string $baseName - Name of the project to search for - * @return boolean true if project found - */ - public function projectExists($baseName) - { - $projectName = self::getCodeBuildProjectName($baseName); - echo "Check project " . $projectName . " exists" . PHP_EOL; - $retVal = true; - $result = $this->codeBuildClient->batchGetProjects([ - 'names' => [ - $projectName - ] - ]); - $projects = $result['projects']; - if (count($projects) == 0) { - $retVal = false; - } - return $retVal; - } - - private function addEnvironmentToArray($environmentVariables, $environment) - { - if ($environment == 'null') { - $environment = null; - } - if (!is_null($environment)) { - try { - $environmentArray = json_decode($environment); - foreach ($environmentArray as $key => $value) { - $entry = [ - 'name' => $key, - 'value' => $value, - ]; - $environmentVariables[] = $entry; - } - } catch (\Exception $e) { - echo ("Exception caught and ignored"); - } - } - return $environmentVariables; - } - - private function getImageTag($environmentVariables) { - $imageTag = self::getCodeBuildImageTag(); - foreach ($environmentVariables as $entry) { - if ($entry['name'] === "BUILD_IMAGE_TAG") { - $imageTag = $entry['value']; - break; - } - } - return $imageTag; - } - - private function getComputeType($environmentVariables) { - $computeType = "BUILD_GENERAL1_SMALL"; - foreach ($environmentVariables as $entry) { - if ($entry['name'] === "BUILD_COMPUTE_TYPE") { - $value = $entry['value']; - switch ($value) { - case "small": $computeType = "BUILD_GENERAL1_SMALL"; break; - case "medium": $computeType = "BUILD_GENERAL1_MEDIUM"; break; - case "large": $computeType = "BUILD_GENERAL1_LARGE"; break; - case "2xlarge": $computeType = "BUILD_GENERAL1_2XLARGE"; break; - } - break; - } - } - return $computeType; - } -} diff --git a/application/common/components/CodeCommit.php b/application/common/components/CodeCommit.php deleted file mode 100644 index da32ada7..00000000 --- a/application/common/components/CodeCommit.php +++ /dev/null @@ -1,90 +0,0 @@ -codeCommitClient = \Yii::$container->get('codeCommitClient'); - } catch (\Exception $e) { - // Get real S3 client - $this->codeCommitClient = self::getCodeCommitClient(); - } - } - /** - * Configure and get the CodeCommit Client - * @return \Aws\CodeBuild\CodeCommitClient - */ - public static function getCodeCommitClient() - { - $client = new \Aws\CodeCommit\CodeCommitClient([ - 'region' => self::getArtifactsBucketRegion(), - 'version' => '2015-04-13' - ]); - return $client; - } - /** - * Returns http url of code commit archive derived from git url needed for CodeBuild - * - * @param string $git_url - * @return string http codecommit url - */ - public function getSourceURL($git_url) { - $prefix = Utils::getPrefix(); - echo "[$prefix] getSourceURL URL: " .$git_url . PHP_EOL; - $repo = substr($git_url, strrpos($git_url, '/') + 1); - $repoInfo = $this->codeCommitClient->getRepository([ - 'repositoryName' => $repo - ]); -// var_dump($repoInfo['repositoryMetadata']); -// echo "repoInfo: " . $repoInfo['repositoryMetadata'] . PHP_EOL; - $metadata = $repoInfo['repositoryMetadata']; - echo "cloneUrl: " . $metadata['cloneUrlHttp'] . PHP_EOL; - $cloneUrl = $metadata['cloneUrlHttp']; - return $cloneUrl; - } - /** - * Return ssh url of code commit archive derived from git url needed for CodeBuild - * - * @param string $git_url - * @return string http codecommit url - */ - public function getSourceSshURL($git_url) { - $prefix = Utils::getPrefix(); - echo "[$prefix] getSourceURL URL: " .$git_url . PHP_EOL; - $repo = substr($git_url, strrpos($git_url, '/') + 1); - $repoInfo = $this->codeCommitClient->getRepository([ - 'repositoryName' => $repo - ]); - $metadata = $repoInfo['repositoryMetadata']; - echo "cloneUrl: " . $metadata['cloneUrlSsh'] . PHP_EOL; - $cloneUrl = $metadata['cloneUrlSsh']; - return $cloneUrl; - } - - /** - * Returns commit id of the specified branch for the specified repo - * - * @param string $git_url - * @param string $branch - * @return string commit id - */ - public function getCommitId($git_url, $branch) { - $prefix = Utils::getPrefix(); - echo "[$prefix] getCommitId URL: " .$git_url . " Branch: " . $branch . PHP_EOL; - $repo = substr($git_url, strrpos($git_url, '/') + 1); - $result = $this->codeCommitClient->getBranch([ - 'branchName' => $branch, - 'repositoryName' => $repo, - ]); - $branchInfo = $result['branch']; - $commitId = $branchInfo['commitId']; - echo "commitId: " . $commitId . PHP_EOL; - return $commitId; - } -} diff --git a/application/common/components/EmailUtils.php b/application/common/components/EmailUtils.php deleted file mode 100644 index 363fc12a..00000000 --- a/application/common/components/EmailUtils.php +++ /dev/null @@ -1,255 +0,0 @@ -params; - if (Utils::isArrayEntryTruthy($config, $maxParam) ) { - return $config[$maxParam]; - } - //default - return 6; - } - - public static function getMaxEmails() - { - $maxParam = 'max_emails_per_try'; - $config = \Yii::$app->params; - if (Utils::isArrayEntryTruthy($config, $maxParam) ) { - return $config[$maxParam]; - } - - return 21; - } - - public static function getAdminEmailAddress() - { - $adminEmailParam = 'adminEmail'; - $config = \Yii::$app->params; - if (Utils::isArrayEntryTruthy($config, $adminEmailParam) ) { - return $config[$adminEmailParam]; - } - - return 'nobody@nowhere.com'; - } - /** - * @param $email a Mailer email object - * @return array with two elements ... - * - bool whether the email got sent successfully - * - string for a possible error method - */ - public static function trySendingEmail($email) { - - $logger = new Appbuilder_logger("EmailUtils"); - try { - $gotSent = $email->send(); - - // log email sent - if ($gotSent) { - $logContents = [ - 'to' => $email->to, - 'subject' => $email->subject - ]; - $logger->appbuilderInfoLog($logContents); - } - - return [$gotSent, ""]; - } catch (\Exception $e) { - // the to property seems to come as ... array('to@dom.com' => null); - if (is_array($email->to)) { - if(!empty($email->to)) { - $to = array_keys($email->to)[0]; - } else { - $to = "No To Address"; - } - } else { - $to = $email->to; - } - - $logContents = [ - 'to' => $to, - 'error_code' => $e->getCode(), - 'error_message' => $e->getMessage(), - ]; - $logger->appbuilderExceptionLog($logContents, $e); - - $errorMsg = 'Error code: ' . $e->getCode() . '. ' . $e->getMessage(); - return [false, $errorMsg]; - } - } - - - - /** - * Sends emails for all the EmailQueue entries. - * Also, adds and EventLog record regarding the email being sent. - * - * @param \Mailer [optional] for unit testing - * @return array with two elements ... - * 1) an array with the pk's and to address of the emails that were sent - * 2) an array of the error messages that were encountered - */ - public static function sendEmailQueue($mailer=null) - { - if ($mailer === null) { - $mailer = \Yii::$app->mailer; - } - - $maxAttempts = self::getMaxAttempts(); - - $maxEmails = self::getMaxEmails(); - $emailQueues = EmailQueue::find() - ->where('attempts_count <= ' . $maxAttempts) - ->orWhere(['attempts_count' => null]) - ->limit($maxEmails) - ->all(); - - $errors = array(); - $eQueuesSent = array(); - - foreach($emailQueues as $nextEQueue) { - // for reporting the results - $eQueueID = 'EmailQueue ' . $nextEQueue->id . ': ' . $nextEQueue->to; - - $newEmail = $mailer->compose() - ->setFrom(EmailQueue::getFromAddress()) - ->setTo($nextEQueue->to) - ->setSubject($nextEQueue->subject) - ->setTextBody($nextEQueue->text_body); - - $setMethods = ['setCc' => $nextEQueue->cc, - 'setBcc' => $nextEQueue->bcc, - 'setHtmlBody' => $nextEQueue->html_body]; - - foreach ($setMethods as $method => $value) { - if ($value) { - $newEmail->$method($value); - } - } - - list($sent, $errorMsg) = self::trySendingEmail($newEmail); - - if ( ! $sent) { - $errors[] = $eQueueID . ". Tried to send but failed."; - - $now = new \DateTime(); - $now = $now->format(self::DT_Format); - - $nextEQueue->error = $errorMsg; - $nextEQueue->attempts_count++; - $nextEQueue->last_attempt = $now; - $nextEQueue->save(); - continue; - } - - $eQueuesSent[] = $eQueueID; - - $logger = new Appbuilder_logger("EmailUtils"); - $logContents = [ - 'to' => $nextEQueue->to, - 'subject' => $nextEQueue->subject - ]; - - $logger->appbuilderInfoLog($logContents); - $nextEQueue->delete(); - } - - return array($eQueuesSent, $errors); - } - - - - /** - * Attemps to send an email. - * If an exception is thrown, does a Yii:error log, - * creates a corresponding EmailQueue entry and returns false. - * If the email fails to send, creates a corresponding EmailQueue entry - * and returns false. - * Otherwise, returns true. - * - * - * @param string $to - * @param string $subject - * @param string $textBody - * @param string $cc - * @param string $bcc - * @param null $htmlBody - * @param null $mailer for unit testing - * @return bool - */ - public static function sendEmailOrQueueIt($to, $subject, $textBody, $cc, $bcc, - $htmlBody = null, - $mailer = null) - { - - if ($mailer === null) { - $mailer = \Yii::$app->mailer; - } - - $from = EmailQueue::getFromAddress(); - - $newEmail = $mailer->compose() - ->setFrom($from) - ->setTo($to) - ->setSubject($subject) - ->setTextBody($textBody); - - $setMethods = ['setCc' => $cc, 'setBcc' => $bcc, 'setHtmlBody' => $htmlBody]; - - foreach ($setMethods as $method => $value) { - if ($value) { - $newEmail->$method($value); - } - } - - list($sent, $errorMsg) = self::trySendingEmail($newEmail); - - if ($sent) { - return true; - } - - self::QueueEmail($to, $subject, $textBody, $cc, $bcc, $htmlBody); - - return false; - } - - - /** - * Creates an EmailQueue entry, saves it and returns it. - * - * @param string $to - * @param string $subject - * @param string $textBody - * @param string $cc - * @param string $bcc - * @param null $htmlBody - * @return bool - */ - public static function QueueEmail($to, $subject, $textBody, $cc, $bcc, - $htmlBody = null) - { - $emailQueue = new EmailQueue; - $emailQueue->to = $to; - $emailQueue->cc = $cc; - $emailQueue->bcc = $bcc; - $emailQueue->subject = $subject; - $emailQueue->text_body = $textBody; - $emailQueue->html_body = $htmlBody; - - $emailQueue->save(); - - return $emailQueue; - } -} \ No newline at end of file diff --git a/application/common/components/FileUtils.php b/application/common/components/FileUtils.php deleted file mode 100644 index cd9fd829..00000000 --- a/application/common/components/FileUtils.php +++ /dev/null @@ -1,51 +0,0 @@ -iamClient = \Yii::$container->get('iAmClient'); - $this->ut = true; - } catch (\Exception $e) { - // Get real S3 client - $this->iamClient = self::getIamClientNoCredentials(); - $this->ut = false; - } - $this->fileUtil = \Yii::$container->get('fileUtils'); - } - function getIamClientNoCredentials(){ - $awsRegion = \Yii::$app->params['awsRegion']; - return IamClient::factory([ - 'region' => $awsRegion, - 'version' => '2010-05-08', - ]); - } - - function getIamClient(){ - if ($this->ut == true) { - return $this->iamClient; - } - $awsKey = \Yii::$app->params['awsKeyId']; - $awsSecret = \Yii::$app->params['awsSecretKey']; - $awsRegion = \Yii::$app->params['awsRegion']; - return IamClient::factory([ - 'region' => $awsRegion, - 'version' => '2010-05-08', - 'credentials' => [ - 'key' => $awsKey, - 'secret' => $awsSecret, - ] - ]); - } - function getCodecommitClient() - { - $awsKey = \Yii::$app->params['awsKeyId']; - $awsSecret = \Yii::$app->params['awsSecretKey']; - $awsRegion = \Yii::$app->params['awsRegion']; - - return CodeCommitClient::factory([ - 'region' => $awsRegion, - 'version' => '2015-04-13', - 'credentials' => [ - 'key' => $awsKey, - 'secret' => $awsSecret, - ] - ]); - } - - /** - * Determines whether the role for the specified project - * and the current production stage exists - * - * @param string $projectName - base project name, i.e. build_app or publish_app - * @return boolean - true if role associated with base project exists for this production stage - */ - public function doesRoleExist($projectName) - { - try - { - $fullRoleName = self::getRoleName($projectName); - echo "Check role " . $fullRoleName . " exists" . PHP_EOL; - $result = $this->iamClient->getRole([ - 'RoleName' => $fullRoleName, // REQUIRED - ]); - return true; - } catch (\Exception $e) { - return false; - } - } - /** - * This method returns the role arn - * @param string $projectName - base project name, i.e. build_app or publish_app - * @return string arn for role - */ - public function getRoleArn($projectName) - { - try - { - $fullRoleName = self::getRoleName($projectName); - $result = $this->iamClient->getRole([ - 'RoleName' => $fullRoleName, - ]); - $role = $result['Role']; - $roleArn = $role['Arn']; - echo 'Role Arn is ' . $roleArn . PHP_EOL; - return $roleArn; - } catch (\Exception $e) { - return ""; - } - } - - /** - * gets the iam arn of a specific iam policy - * - * @param string base policy name, e.g. s3-appbuild-secrets - * @return string arn for policy - */ - public static function getPolicyArn($basePolicyName) { - $prefix = 'arn:aws:iam::'; - $userId = self::getAWSUserAccount(); - $productStage = self::getAppEnv(); - $arn = $prefix . $userId . ":policy/" . $basePolicyName . "-" . $productStage; - return $arn; - } - /** - * gets the iam user if it exists or creates one if it does not - * - * @param string $user_id - Project user id - * @return User User from Iam; - */ - public function createAwsAccount($user_id) - { - $iamClient = $this->getIamClient(); - - try { - $user = $iamClient->createUser([ - 'Path' => '/sab-codecommit-users/', - 'UserName' => $user_id - ]); - - return $user; - } catch (IamException $e) { - if($e->getAwsErrorCode() == 'EntityAlreadyExists') { // They already have an account - pass back their account - $user = $iamClient->getUser([ - 'UserName' => $user_id - ]); - - return $user; - } - - $message = sprintf('SAB: Unable to create account. code=%s : message=%s', $e->getCode(), $e->getMessage()); - \Yii::error($message, 'service'); - throw new ServerErrorHttpException($message, 1437423331, $e); - } - } - - /** - * adds a user to the specified IAM Group - * - * @param string $userName - * @param string $groupName - * @return IAM always returns empty array so that is what is being returned - */ - public function addUserToIamGroup($userName, $groupName) - { - $iamClient = $this->getIamClient(); - - $result = $iamClient->addUserToGroup([ - 'GroupName' => $groupName, - 'UserName' => $userName - ]); - - return $result; - } - /** - * removes a user to the specified IAM Group - * - * @param string $userName - * @param string $groupName - * @return IAM always returns empty array so that is what is being returned - */ - public function removeUserFromIamGroup($userName, $groupName) - { - $iamClient = $this->getIamClient(); - - $result = $iamClient->removeUserFromGroup([ - 'GroupName' => $groupName, - 'UserName' => $userName - ]); - - return $result; - } - - /** - * adds the public key for a user to IAM - * - * @param string $username - The name of the user associated with the key - * @param string $publicKey - The ssh key for the user - * @return SSHPublicKey - See AWS API documentation - */ - public function addPublicSshKey($username, $publicKey) - { - $iamClient = $this->getIamClient(); - try { - $result = $iamClient->uploadSSHPublicKey([ - 'SSHPublicKeyBody' => $publicKey, - 'UserName' => $username - ]); - - return $result; - } catch (IamException $e) { - if($e->getAwsErrorCode()=='DuplicateSSHPublicKey'){ - try{ - $keysForRequester = $iamClient->listSSHPublicKeys([ - 'UserName' => $username - ]); - - foreach ($keysForRequester['SSHPublicKeys'] as $requesterKey) { - $key = $iamClient->getSSHPublicKey([ - 'UserName' => $username, - 'SSHPublicKeyId' => $requesterKey['SSHPublicKeyId'], - 'Encoding' => 'SSH' - ]); - - if ($this->isEqual($key['SSHPublicKey']['SSHPublicKeyBody'], $publicKey)) { - return $key; - } - } - - $message = sprintf('SAB: Unable to find a matching ssh key for user. code=%s : message=%s', $e->getCode(), $e->getMessage()); - throw new ServerErrorHttpException($message, 1451819839); - } catch (IamException $e) { - $message = sprintf('SAB: Unable to get users existing ssh key(s). code=%s : message=%s', $e->getCode(), $e->getMessage()); - throw new ServerErrorHttpException($message, 1441819828, $e); - } - } - - $message = sprintf('SAB: Unable to add ssh key to user. code=%s : message=%s', $e->getCode(), $e->getMessage()); - throw new ServerErrorHttpException($message, 1441809827, $e); - } - } - /** - * used to determine whether two openSSH formatted keys are equal (without regard to comment portion of key) - * @param $openSSHKey1 format expected: type data comment - * @param $openSSHKey2 format expected: type data comment - * @return bool - */ - private function isEqual($openSSHKey1, $openSSHKey2) { - list($type1, $data1) = explode(" ", $openSSHKey1); - list($type2, $data2) = explode(" ", $openSSHKey2); - - if ($type1 == $type2 && $data1 == $data2) { - return true; - } - - return false; - } -} diff --git a/application/common/components/JenkinsUtils.php b/application/common/components/JenkinsUtils.php deleted file mode 100644 index e569c435..00000000 --- a/application/common/components/JenkinsUtils.php +++ /dev/null @@ -1,173 +0,0 @@ -getJenkinsBaseUrl(); - $jenkins = new Jenkins($jenkinsUrl); - return $jenkins; - } - /** - * @return string - */ - public function getJenkinsBaseUrl(){ - return \Yii::$app->params['buildEngineJenkinsMasterUrl']; - } - /** - * - * @return Jenkins for publish url - */ - public function getPublishJenkins(){ - $jenkinsUrl = $this->getPublishJenkinsBaseUrl(); - $jenkins = new Jenkins($jenkinsUrl); - return $jenkins; - } - public function getPublishJenkinsBaseUrl() { - return \Yii::$app->params['publishJenkinsMasterUrl']; - } - /** - * @param JenkinsBuild $jenkinsBuild - * @return array|null - */ - public function getArtifactUrls($jenkinsBuild) { - - $jenkinsArtifacts = $jenkinsBuild->get("artifacts"); - if (!$jenkinsArtifacts) { return null; } - $artifactUrls = array(); - $artifactRelativePaths = array(); - foreach ($jenkinsArtifacts as $testArtifact) { - $relativePath = explode("output/", $testArtifact->relativePath)[1]; - array_push($artifactRelativePaths, $relativePath); - $artifactUrl = $this->getArtifactUrlFromRelativePath($jenkinsBuild, $testArtifact->relativePath); - array_push($artifactUrls, $artifactUrl); - } - return array($artifactUrls, $artifactRelativePaths); - } - /** - * @param JenkinsBuild $jenkinsBuild - * @param string $relativePath - * @return string - */ - public function getArtifactUrlFromRelativePath($jenkinsBuild, $relativePath) { - $encode = function($value) { - return rawurlencode($value); - }; - $encodedPath = implode("/", array_map($encode,explode("/", $relativePath))); - $baseUrl = $jenkinsBuild->getJenkins()->getBaseUrl(); - $buildUrl = $jenkinsBuild->getBuildUrl(); - $pieces = explode("job", $buildUrl); - $artifactUrl = $baseUrl."job".$pieces[1]."artifact/".$encodedPath; - return $artifactUrl; - } - /** - * Extract the Artifact Url from the Jenkins Build information. - * @param JenkinsBuild $jenkinsBuild - * @return string - */ - public function getApkArtifactUrl($jenkinsBuild) - { - return $this->getArtifactUrl($jenkinsBuild, "/\.apk$/"); - } - /** - * Extract the Artifact Url from the Jenkins Build information. - * @param JenkinsBuild $jenkinsBuild - * @return string - */ - public function getVersionCodeArtifactUrl($jenkinsBuild) - { - return $this->getArtifactUrl($jenkinsBuild, "/version_code.txt/"); - } - /** - * Extract the Artifact Url from the Jenkins Build information. - * @param JenkinsBuild $jenkinsBuild - * @return string - */ - public function getPackageNameArtifactUrl($jenkinsBuild) - { - return $this->getArtifactUrl($jenkinsBuild, "/package_name.txt/"); - } - /** - * Extract the Artifact Url from the Jenkins Build information. - * @param JenkinsBuild $jenkinsBuild - * @return string - */ - public function getMetaDataArtifactUrl($jenkinsBuild) - { - return $this->getArtifactUrl($jenkinsBuild, "/publish.tar.gz/"); - } - /** - * Extract the Artifact Url from the Jenkins Build information. - * @param JenkinsBuild $jenkinsBuild - * @return string - */ - public function getAboutArtifactUrl($jenkinsBuild) - { - return $this->getArtifactUrl($jenkinsBuild, "/about.txt/"); - } - /** - * Extract the Artifact Url from the Jenkins Build information. - * @param JenkinsBuild $jenkinsBuild - * @param string $artifactPattern - * @return string - */ - private function getArtifactUrl($jenkinsBuild, $artifactPattern) - { - $artifacts = $jenkinsBuild->get("artifacts"); - if (!$artifacts) { return null; } - $artifact = null; - foreach ($artifacts as $testArtifact) { - if(preg_match($artifactPattern,$testArtifact->relativePath)) { - $artifact = $testArtifact; - break; - } - } - if (!$artifact) { - echo "getArtifactURL: No artifact matching ".$artifactPattern . PHP_EOL; - return null; - } - $relativePath = $artifact->relativePath; - $baseUrl = $jenkinsBuild->getJenkins()->getBaseUrl(); - $buildUrl = $jenkinsBuild->getBuildUrl(); - $pieces = explode("job", $buildUrl); - return $baseUrl."job".$pieces[1]."artifact/".$relativePath; - } - /** - * - * get build details for logging. - * @param Build $build - * @return array - */ - public static function getlogBuildDetails($build) - { - $jobName = $build->job->name(); - $log = [ - 'jobName' => $jobName - ]; - $log['buildId'] = $build->id; - $log['buildStatus'] = $build->status; - $log['buildNumber'] = $build->build_number; - $log['buildResult'] = $build->result; - if (!is_null($build->artifact_url_base)) { - $log['buildArtifactUrlBase'] = $build->artifact_url_base; - } - if (!is_null($build->artifact_files)) { - $log['buildArtifactFiles'] = $build->artifact_files; - } - - echo "Job=$jobName, Id=$build->id, Status=$build->status, Number=$build->build_number, " - . "Result=$build->result, ArtifactUrlBase=$build->artifact_url_base, ArtifactFiles=$build->artifact_files". PHP_EOL; - return $log; - } - -} diff --git a/application/common/components/S3.php b/application/common/components/S3.php deleted file mode 100644 index 3871b549..00000000 --- a/application/common/components/S3.php +++ /dev/null @@ -1,388 +0,0 @@ -s3Client = \Yii::$container->get('s3Client'); - $this->ut = true; - } catch (\Exception $e) { - // Get real S3 client - $this->s3Client = self::getS3Client(); - $this->ut = false; - } - $this->fileUtil = \Yii::$container->get('fileUtils'); - } - - - /** - * Configure and get the S3 Client - * @return \Aws\S3\S3Client - */ - public static function getS3Client() - { - $client = new \Aws\S3\S3Client([ - 'region' => S3::getArtifactsBucketRegion(), - 'version' => '2006-03-01' - ]); - $client->registerStreamWrapper(); - return $client; - } - public function getS3ClientWithCredentials(){ - if ($this->ut == true) { - return $this->s3Client; - } - $awsKey = \Yii::$app->params['awsKeyId']; - $awsSecret = \Yii::$app->params['awsSecretKey']; - $client = new \Aws\S3\S3Client([ - 'region' => S3::getArtifactsBucketRegion(), - 'version' => '2006-03-01', - 'credentials' => [ - 'key' => $awsKey, - 'secret' => $awsSecret, - ] - ]); - return $client; - } - - /** - * gets the s3 arn of a specific file - * - * @param Build $build Current build object - * @param string $productStage - stg or prd - * @param string $filename - Name of s3 file that arn is requested for - * @return string prefix - */ - public static function getS3Arn($build, $productStage, $filename) { - $prefix = 'arn:aws:s3:::'; - $bucket = self::getArtifactsBucket(); - $baseUrl = $build->getBasePrefixUrl($productStage); - $arn = $prefix . $bucket . "/" . $baseUrl . "/"; - if (!empty($filename)) { - $arn = $arn . $filename; - } - return $arn; - } - /** - * This function reads a file from the build output - * - * @param Build $build Current build object - * @param string $fileName Name of the file without path - * @return string Contains the contents of the file - */ - public function readS3File($artifacts_provider, $fileName){ - $fileContents = ""; - try { - $filePath = $artifacts_provider->getBasePrefixUrl('codebuild-output') . "/" . $fileName; - $result = $this->s3Client->getObject([ - 'Bucket' => self::getArtifactsBucket(), - 'Key' => $filePath, - ]); - $fileContents = (string) $result['Body']; - } catch (AwsException $e) { - // There is not a good way to check for file exists. If file doesn't exist, - // it will be caught here and an empty string returned. - echo "readS3File: exception caught. Type: " . $e->getAwsErrorType() . PHP_EOL; - } - return $fileContents; - } - - /** - * copyS3BuildFolder copies the files from where they have been saved encrypted in s3 by codebuild - * to the final unencrypted artifacts folder. - * NOTE: This move is required because the initial codebuild version encrypts the files - * with a key and there is no option to build them without encryption. - * - * @param Build or Release $artifacts_provider - The build or release - */ - public function copyS3Folder($artifacts_provider){ - $artifactsBucket = self::getArtifactsBucket(); - $sourcePrefix = $artifacts_provider->getBasePrefixUrl('codebuild-output') . "/"; - $destPrefix = $artifacts_provider->getBasePrefixUrl(self::getAppEnv()) . "/"; - $publicBaseUrl = $this->s3Client->getObjectUrl($artifactsBucket, $destPrefix); - $artifacts_provider->beginArtifacts($publicBaseUrl); - $destFolderPrefix = $artifacts_provider->getBasePrefixUrl(self::getAppEnv()); - try - { - // If destination folder already exists from some previous build, delete it - $this->s3Client->deleteMatchingObjects($artifactsBucket, $destFolderPrefix); - } catch (DeleteMultipleObjectsException $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] copyS3Build - Folder: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - } - $result = $this->s3Client->listObjectsV2([ - 'Bucket' => $artifactsBucket, - 'Prefix' => $sourcePrefix, - ]); - $files = $result['Contents']; - if (!is_null($files)) - { - foreach ($files as $file) { - $this->copyS3File($file, $sourcePrefix, $destPrefix, $artifacts_provider); - } - } - } - - /** - * This method copies a single file from the encrypted source archive to - * the unencrypted destination archive - * - * @param AWS/File $file - AWS object for source file - * @param string $sourcePrefix - The AWS path to the source folder - * @param string $destPrefix - The AWS path to the destination folder - * @param ArtifactsProvider $artifacts_provider - Successful build associated with the copy - */ - public function copyS3File($file, $sourcePrefix, $destPrefix, $artifacts_provider) { - $artifactsBucket = self::getArtifactsBucket(); - $fileContents=""; - $fileNameWithPrefix = $file['Key']; - $fileName = substr($fileNameWithPrefix, strlen($sourcePrefix)); - switch ($fileName) { - case 'manifest.txt': - return; - case 'play-listing/default-language.txt': - return; - //case: 'version.json': FUTURE: get versionCode from version.json - case 'version_code.txt': - $fileContents = (string)$this->readS3File($artifacts_provider, $fileName); - break; - default: - echo $fileName . PHP_EOL; - break; - } - $sourceDir = dirname($fileNameWithPrefix); - $sourceBasename = basename($fileNameWithPrefix); - $sourceFile = $artifactsBucket . '/' . $sourceDir . '/' . urlencode($sourceBasename); - $destinationFile = $destPrefix . $fileName; - try { - $return = $this->s3Client->copyObject([ - 'Bucket' => $artifactsBucket, - 'CopySource' => $sourceFile, - 'Key' => $destinationFile, - 'ACL' => 'public-read', - 'ContentType' => $this->getFileType($fileName), - 'MetadataDirective' => 'REPLACE', - ]); - $artifacts_provider->handleArtifact($destinationFile, $fileContents); - } catch (\Exception $e) { - echo "File was not renamed " . $sourceFile . PHP_EOL . "exception: " . $e->getTraceAsString(); - } - } - - public function writeFileToS3($fileContents, $fileName, $artifacts_provider) { - $fileS3Bucket = self::getArtifactsBucket(); - $destPrefix = $artifacts_provider->getBasePrefixUrl(self::getAppEnv()); - $fileS3Key = $destPrefix . "/" . $fileName; - - $this->s3Client->putObject([ - 'Bucket' => $fileS3Bucket, - 'Key' => $fileS3Key, - 'Body' => $fileContents, - 'ACL' => 'public-read', - 'ContentType' => $this->getFileType($fileName) - ]); - - $artifacts_provider->handleArtifact($fileS3Key, $fileContents); - } - public function removeCodeBuildFolder($artifacts_provider) { - $s3Folder = $artifacts_provider->getBasePrefixUrl('codebuild-output') . "/"; - $s3Bucket = S3::getArtifactsBucket(); - echo ("Deleting S3 bucket: $s3Bucket key: $s3Folder").PHP_EOL; - $this->s3Client->deleteMatchingObjects($s3Bucket, $s3Folder); - } - - private function getFileType($fileName) { - $info = pathinfo($fileName); - if (!array_key_exists('extension', $info)) { - return "application/octet-stream"; - } - - switch ($info['extension']) { - case "html": - $contentType = "text/html"; - break; - case "png": - $contentType = "image/png"; - break; - case "jpg": - case "jpeg": - $contentType = "image/jpeg"; - break; - case "txt": - case "log": - $contentType = "text/plain"; - break; - case "json": - $contentType = "application/json"; - break; - default: - $contentType = "application/octet-stream"; - break; - } - return $contentType; - } - - /** - * Get the S3 Key to use to archive a build - * @param Build $build - * @param string $relativePath - * @return string S3Key - */ - public static function getS3Key($build, $relativePath) - { - $job = $build->job; - return self::getS3KeyByNameNumber($job->nameForBuild(), $build->build_number, $relativePath); - } - - /** - * Get the S3 Key to use to archive a build - * @param Build $build - * @return string S3Key - */ - public static function getS3KeyBase($build) { - $job = $build->job; - return self::getS3KeyBaseByNameNumber($job->nameForBuild(), $build->id); - } - - private static function getS3KeyBaseByNameNumber($name, $number) { - return self::getAppEnv()."/jobs/".$name."/".$number."/"; - } - - private static function getS3KeyByNameNumber($name, $number, $relativePath) { - return self::getS3KeyBaseByNameNumber($name, $number) .$relativePath; - } - - /** - * Removes any S3 job folder that doesn't have a corresponding - * record in the db - * - * @param array $jobNames - Array of all of the job names - */ - public function removeS3FoldersWithoutJobRecord($jobNames) - { - $logInfo = ["Checking for S3 files to delete"]; - $bucket = S3::getArtifactsBucket(); - echo "ArtifactsBucket $bucket".PHP_EOL; - - // Create a list of all of the files in S3 in this bucket. - $prefix = \Yii::$app->params['appEnv']."/jobs/"; - $s3FolderArray = self::getS3JobArray($bucket, $prefix); - // Now check and see if a record exists in the job table for - // those S3 folders and delete the S3 folder if not - foreach ($s3FolderArray as $key => $value) { - if (!array_key_exists($key, $jobNames)) - { - $folderKey = $prefix.$key."/"; - echo ("Deleting S3 bucket: $bucket key: $folderKey").PHP_EOL; - $this->s3Client->deleteMatchingObjects($bucket, $folderKey); - $logString = "Deleted S3 bucket: $bucket key: $folderKey".PHP_EOL; - $logInfo[] = $logString; - } - } - return $logInfo; - } - - private function getS3JobArray($bucket, $prefix) - { - echo "Bucket: $bucket Prefix: $prefix".PHP_EOL; - $prefixLength = strlen($prefix); - $results = $this->s3Client->getPaginator('ListObjects', [ - 'Bucket' => $bucket, - 'Prefix' => $prefix - ]); - // Create an array of just the jobs associated with those files - $s3FolderArray = array(); - foreach ($results as $result) { - foreach ($result['Contents'] as $object) { - $key = $object['Key']; - $build = substr($key, $prefixLength, strpos($key, '/', $prefixLength) - $prefixLength); - if (!array_key_exists($build, $s3FolderArray)) - { - $s3FolderArray[$build] = 1; - } - } - } - return $s3FolderArray; - } - private function getS3ProjectArray($bucket, $prefix) - { - echo "Bucket: $bucket Prefix: $prefix".PHP_EOL; - $client = $this->getS3ClientWithCredentials(); - $prefixLength = strlen($prefix); - $results = $client->getPaginator('ListObjects', [ - 'Bucket' => $bucket, - 'Prefix' => $prefix - ]); - // Create an array of just the projects associated with those files - $s3FolderArray = array(); - foreach ($results as $result) { - $contents = $result['Contents']; - if ($contents == null) { - continue; - } - foreach ($contents as $object) { - $key = $object['Key']; - $build = substr($key, $prefixLength + 1, strpos($key, '/', $prefixLength + 1) - ($prefixLength + 1)); - if (!(is_null($build) || empty($build))) - { - if (!array_key_exists($build, $s3FolderArray)) - { - $s3FolderArray[$build] = 1; - } - } - } - } - return $s3FolderArray; - } - - /** - * Remove the artifacts for this build saved in S3 - * - * @param Build $build - */ - public function removeS3Artifacts($build) { - $parts = parse_url($build->artifact_url_base); - $path = $parts['path']; - $bucket = substr($path,1, strpos( $path, '/', 1) - 1); - $key = substr($path, strpos($path, '/', 1) + 1); - $this->s3Client->deleteMatchingObjects($bucket, $key); - echo "Deleted S3 bucket $bucket key $key " . PHP_EOL; - } - /** - * Copy a folder to s3 - */ - public function uploadFolder($folderName, $bucket, $keyPrefix = null) { - $client = $this->getS3ClientWithCredentials(); - $client->uploadDirectory($folderName, $bucket, $keyPrefix); - } - - public function doesObjectExist($bucket, $key, $project) { - $client = $this->getS3ClientWithCredentials(); - $exists = false; - $s3FolderArray = $this->getS3ProjectArray($bucket, $key); - if (array_key_exists($project, $s3FolderArray)) - { - $exists = true; - } - return $exists; - } -} diff --git a/application/common/components/STS.php b/application/common/components/STS.php deleted file mode 100644 index 6a654bf5..00000000 --- a/application/common/components/STS.php +++ /dev/null @@ -1,176 +0,0 @@ -stsClient = \Yii::$container->get('stsClient'); - } catch (\Exception $e) { - // Get real STS client - $this->stsClient = self::getStsClient(); - } - } - - /** - * Configure and get the S3 Client - * @return \Aws\Sts\StsClient - */ - public static function getStsClient() - { - $client = new \Aws\Sts\StsClient([ - 'region' => S3::getArtifactsBucketRegion(), - 'version' => '2011-06-15' - ]); - return $client; - } - - /** - * @param string $name - name of federated user - * @param string $policy - IAM policy in json format - * @param bool $readOnly - is it readonly - * @return array - array of credentials needed for using AWS resources - */ - public function getFederationToken($name, $policy, $readOnly) - { - $result = $this->stsClient->GetFederationToken([ - 'Name' => $name, - 'Policy' => $policy - ]); - $credentials = $result['Credentials']; - $credentials['Region'] = self::getArtifactsBucketRegion(); - $credentials['ReadOnly'] = $readOnly; - return $credentials; - } - - public function getProjectAccessToken($project, $externalId, $readOnly) - { - // https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sts-2011-06-15.html#getfederationtoken - // AWS limits the name: - // The regex used to validate this parameter is a string of characters consisting of - // upper- and lower-case alphanumeric characters with no spaces. You can also include - // underscores or any of the following characters: =,.@- - // https://docs.aws.amazon.com/STS/latest/APIReference/API_GetFederationToken.html - // Max of 32 characters - $externalIdParts = explode('|', $externalId); - // Make sure valid characters are used - $idPart = preg_replace('/[^a-zA-Z0-9_=,.@-]/', '_',end($externalIdParts)); - // Pad out name with randomness - $random = bin2hex(openssl_random_pseudo_bytes(16)); - // Use max 32 characters - $tokenName = substr($idPart . "." . $random,0,32); - $policy = $readOnly ? self::getReadOnlyPolicy($project) : self::getReadWritePolicy($project); - return $this->getFederationToken($tokenName, $policy, $readOnly); - } - - /** - * @param Project $project - * @return string - */ - public static function getReadWritePolicy($project) - { - // Note: s3 arns cannot contain region or account id - $policy = '{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::BUCKET", - "Condition": { - "StringLike": { - "s3:prefix": [ - "FOLDER/", - "FOLDER/*" - ] - } - } - }, - { - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:PutObject", - "s3:GetObjectAcl", - "s3:PutObjectAcl", - "s3:GetObjectTagging", - "s3:PutObjectTagging", - "s3:DeleteObject", - "s3:DeleteObjectVersion", - "s3:PutLifeCycleConfiguration" - ], - "Resource": [ - "arn:aws:s3:::BUCKET/FOLDER", - "arn:aws:s3:::BUCKET/FOLDER/*" - ] - } - ] -}'; - return self::getPolicy($project, $policy); - } - /** - * @param Project $project - * @return string - */ - public static function getReadOnlyPolicy($project) { - $policy = '{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::BUCKET", - "Condition": { - "StringLike": { - "s3:prefix": [ - "FOLDER/", - "FOLDER/*" - ] - } - } - }, - { - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:GetObjectAcl", - "s3:GetObjectTagging" - ], - "Resource": [ - "arn:aws:s3:::BUCKET/FOLDER", - "arn:aws:s3:::BUCKET/FOLDER/*" - ] - } - ] -}'; return self::getPolicy($project, $policy); - } - - /** - * @param Project $project - * @return string - */ - public static function getPolicy($project, $policy) { - - $path = $project->getS3ProjectPath(); - $pathParts = explode('/', $path, 2); - $policy = str_replace("BUCKET", $pathParts[0], $policy); - $policy = str_replace("FOLDER", $pathParts[1], $policy); - return $policy; - } -} \ No newline at end of file diff --git a/application/common/config/.gitignore b/application/common/config/.gitignore deleted file mode 100644 index 97c0f016..00000000 --- a/application/common/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php diff --git a/application/common/config/bootstrap.php b/application/common/config/bootstrap.php deleted file mode 100644 index d391b0e6..00000000 --- a/application/common/config/bootstrap.php +++ /dev/null @@ -1,6 +0,0 @@ - 'app-frontend', - 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', - 'components' => [ - 'cache' => [ - 'class' => 'yii\caching\FileCache', - ], - "db" => [ - 'class' => 'yii\db\Connection', - "dsn" => "mysql:host=$MYSQL_HOST;dbname=$MYSQL_DATABASE", - "username" => $MYSQL_USER, - "password" => $MYSQL_PASSWORD, - 'charset' => 'utf8', - 'emulatePrepare' => false, - 'tablePrefix' => '', - ], - "testDb" => [ - "class" => 'yii\db\Connection', - "dsn" => "mysql:host=$TEST_MYSQL_HOST;dbname=$TEST_MYSQL_DATABASE", - "username" => $TEST_MYSQL_USER, - "password" => $TEST_MYSQL_PASSWORD, - "emulatePrepare" => false, - "charset" => "utf8", - "tablePrefix" => "", - ], - 'log' => [ - 'traceLevel' => 0, - 'targets' => [ - [ - 'class' => 'Sil\JsonSyslog\JsonSyslogTarget', - 'levels' => ['error', 'warning'], - 'except' => [ - 'yii\web\HttpException:401', - 'yii\web\HttpException:404', - ], - 'logVars' => [], // Disable logging of _SERVER, _POST, etc. - 'prefix' => function($message) use ($APP_ENV) { - $prefixData = array( - 'env' => $APP_ENV, - ); - /*if (! \Yii::$app->user->isGuest) { - $prefixData['user'] = \Yii::$app->user->identity->email; - }*/ - return \yii\helpers\Json::encode($prefixData); - }, - ], - ], - ], - "mailer" => [ - "class" => 'yii\swiftmailer\Mailer', - "useFileTransport" => $MAILER_USEFILES, - "transport" => [ - "class" => "Swift_SmtpTransport", - "host" => $MAILER_HOST, - "username" => $MAILER_USERNAME, - "password" => $MAILER_PASSWORD, - "port" => "465", - "encryption" => "ssl", - ], - ], - ], - 'params' => [ - 'adminEmail' => $ADMIN_EMAIL, - 'appEnv' => $APP_ENV, - 'max_email_attempts' => 5, - 'max_emails_per_try' => 20, - ], -]; diff --git a/application/common/helpers/Utils.php b/application/common/helpers/Utils.php deleted file mode 100644 index cb4ca819..00000000 --- a/application/common/helpers/Utils.php +++ /dev/null @@ -1,101 +0,0 @@ -generateRandomString($length); - } - - public static function isArrayEntryTruthy($array, $key) - { - return (is_array($array) && isset($array[$key]) && $array[$key]); - } - - /** - * Check if user is logged in and if so return the identity model - * @return null|\common\models\User - */ - public static function getCurrentUser() - { - try { - if(\Yii::$app->user && !\Yii::$app->user->isGuest){ - return \Yii::$app->user->identity; - } - } catch (\Exception $e) { - } - - return null; - } - public static function getPrefix() - { - return date('Y-m-d H:i:s'); - } - /** - * Convert spaces to hyphens and remove all other non letter/number characters - * @param $string - * @return mixed - */ - public static function lettersNumbersHyphensOnly($string) - { - /** - * Convert spaces to hyphens first - */ - $string = str_replace(" ","-",$string); - - /** - * Remove all other non letters/numbers - */ - $string = preg_replace("/[^a-zA-Z0-9\-]/","",$string); - - return $string; - } - /** - * Delete a folder and all files and subfolders it contains - * @param $dir Path to directory - */ - public static function deleteDir($dir) { - if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { - if ($object != "." && $object != "..") { - if (is_dir($dir."/".$object)) - self::deleteDir($dir."/".$object); - else - unlink($dir."/".$object); - } - } - rmdir($dir); - } - } - public static function createGUID() - { - if (function_exists('com_create_guid') === true) - { - return trim(com_create_guid(), '{}'); - } - return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)); - } -} \ No newline at end of file diff --git a/application/common/interfaces/ArtifactsProvider.php b/application/common/interfaces/ArtifactsProvider.php deleted file mode 100644 index 394bd0a2..00000000 --- a/application/common/interfaces/ArtifactsProvider.php +++ /dev/null @@ -1,11 +0,0 @@ - -beginPage() ?> - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - beginBody() ?> - - endBody() ?> - - -endPage() ?> diff --git a/application/common/mail/layouts/text.php b/application/common/mail/layouts/text.php deleted file mode 100644 index 7087ceaf..00000000 --- a/application/common/mail/layouts/text.php +++ /dev/null @@ -1,12 +0,0 @@ - -beginPage() ?> -beginBody() ?> - -endBody() ?> -endPage() ?> diff --git a/application/common/mail/operations/Test/enduser-testmsg.php b/application/common/mail/operations/Test/enduser-testmsg.php deleted file mode 100644 index c4c55f83..00000000 --- a/application/common/mail/operations/Test/enduser-testmsg.php +++ /dev/null @@ -1,10 +0,0 @@ - -Hello , -

    - This is a test message you have received as a result of someone running the - cron/test-emails action. This is a test URL: - - This email can be ignored. -

    diff --git a/application/common/mail/passwordResetToken-html.php b/application/common/mail/passwordResetToken-html.php deleted file mode 100644 index f3daf49f..00000000 --- a/application/common/mail/passwordResetToken-html.php +++ /dev/null @@ -1,15 +0,0 @@ -urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); -?> -
    -

    Hello username) ?>,

    - -

    Follow the link below to reset your password:

    - -

    -
    diff --git a/application/common/mail/passwordResetToken-text.php b/application/common/mail/passwordResetToken-text.php deleted file mode 100644 index 244c0cb4..00000000 --- a/application/common/mail/passwordResetToken-text.php +++ /dev/null @@ -1,12 +0,0 @@ -urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); -?> -Hello username ?>, - -Follow the link below to reset your password: - - diff --git a/application/common/models/Build.php b/application/common/models/Build.php deleted file mode 100644 index 06459ef2..00000000 --- a/application/common/models/Build.php +++ /dev/null @@ -1,679 +0,0 @@ - [ - self::STATUS_ACCEPTED, - ], - self::STATUS_ACCEPTED => [ - self::STATUS_ACTIVE, - ], - self::STATUS_ACTIVE => [ - self::STATUS_COMPLETED, - self::STATUS_EXPIRED, - self::STATUS_POSTPROCESSING, - ], - self::STATUS_EXPIRED => [ - self::STATUS_ACTIVE, - ], - self::STATUS_POSTPROCESSING=> [ - self::STATUS_COMPLETED - ], - ]; - public $validChannelTransitions = [ - self::CHANNEL_UNPUBLISHED => [ - self::CHANNEL_ALPHA, - self::CHANNEL_BETA, - self::CHANNEL_PRODUCTION, - ], - self::CHANNEL_ALPHA => [ - self::CHANNEL_ALPHA, - self::CHANNEL_BETA, - self::CHANNEL_PRODUCTION, - ], - self::CHANNEL_BETA => [ - self::CHANNEL_BETA, - self::CHANNEL_PRODUCTION, - ], - self::CHANNEL_PRODUCTION => [ - self::CHANNEL_PRODUCTION, - ], - ]; - public function createRelease($channel, $targets, $environment) { - $release = new Release(); - $release->build_id = $this->id; - $release->channel = $channel; - $release->targets = $targets; - $release->environment = $environment; - $release->save(); - - return $release; - } - - public function scenarios() - { - return ArrayHelper::merge(parent::scenarios(),[ - - ]); - } - - public function rules() - { - return ArrayHelper::merge(parent::rules(),[ - [ - ['created','updated'],'default', 'value' => Utils::getDatetime(), - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - [ - 'status', 'in', 'range' => [ - self::STATUS_ACTIVE, - self::STATUS_ACCEPTED, - self::STATUS_COMPLETED, - self::STATUS_EXPIRED, - self::STATUS_INITIALIZED, - self::STATUS_POSTPROCESSING, - ], - ], - [ - 'status', 'default', 'value' => self::STATUS_INITIALIZED, - ], - [ - 'job_id', 'exist', 'targetClass' => 'common\models\Job', 'targetAttribute' => 'id', - 'message' => \Yii::t('app', 'Invalid Job ID'), - ], - [ - 'artifact_url_base', 'url', - 'pattern' => '/^https:\/\//', - 'message' => \Yii::t('app', 'Artifact Url must be an https S3 Url.') - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - [ - 'channel', 'in', 'range' => [ - self::CHANNEL_UNPUBLISHED, - self::CHANNEL_ALPHA, - self::CHANNEL_BETA, - self::CHANNEL_PRODUCTION, - ], - ], - [ - 'channel', 'default', 'value' => self::CHANNEL_UNPUBLISHED, - ] - - ]); - } - public function fields() - { - return [ - 'id', - 'job_id', - 'status', - 'result', - 'error', - 'artifacts' => function(){ - return $this->artifacts(); - }, - 'targets', - 'environment', - 'created' => function(){ - return Utils::getIso8601($this->created); - }, - 'updated' => function(){ - return Utils::getIso8601($this->updated); - }, - ]; - } - - private function addIfSet(&$array, $key, $value) { - if (strlen($value)>0) { - $array[$key] = $value; - } - } - public function artifacts() { - $artifacts = array(); - if (strpos($this->targets, "apk") !== false) { - $this->addIfSet($artifacts, self::ARTIFACT_AAB, $this->aab()); - $count = $this->apkCount(); - if ($count > 1) { - $apks = $this->apks(); - foreach ($apks as $apk) { - preg_match('/-([^.]+)\.apk$/', $apk, $matches); - $artifacts[$matches[1] . "-" . self::ARTIFACT_APK] = $apk; - } - } - else { - $this->addIfSet($artifacts, self::ARTIFACT_APK, $this->apk()); - } - $this->addIfSet($artifacts, self::ARTIFACT_ENCRYPTED_KEY, $this->encryptedKey()); - $this->addIfSet($artifacts, self::ARTIFACT_ABOUT, $this->about()); - $this->addIfSet( $artifacts, self::ARTIFACT_DATA_SAFETY_CSV, $this->dataSafetyCsv()); - } - - if (strpos($this->targets, "play-listing") !== false) { - $this->addIfSet($artifacts, self::ARTIFACT_PLAY_LISTING,$this->playListing()); - $this->addIfSet($artifacts, self::ARTIFACT_PLAY_LISTING, $this->playListing()); - $this->addIfSet($artifacts, self::ARTIFACT_PLAY_LISTING_MANIFEST, $this->playListingManifest()); - $this->addIfSet($artifacts, self::ARTIFACT_PLAY_LISTING_DOWNLOAD, $this->playListingDownload()); - $this->addIfSet($artifacts, self::ARTIFACT_VERSION_CODE, $this->versionCode()); - $this->addIfSet($artifacts, self::ARTIFACT_PACKAGE_NAME, $this->packageName()); - $this->addIfSet($artifacts, self::ARTIFACT_PUBLISH_PROPERTIES, $this->publishProperties()); - $this->addIfSet($artifacts, self::ARTIFACT_WHATS_NEW, $this->whatsNew()); - } - - if (strpos($this->targets, "html") !== false) { - $this->addIfSet($artifacts, self::ARTIFACT_HTML, $this->html()); - } - - if (strpos($this->targets, "pwa") !== false) { - $this->addIfSet($artifacts, self::ARTIFACT_PWA, $this->pwa()); - } - - if (strpos($this->targets, "asset-package") !== false) { - $this->addIfSet($artifacts, self::ARTIFACT_ASSET_PACKAGE, $this->assetPackage()); - $this->addIfSet($artifacts, self::ARTIFACT_PACKAGE_NAME, $this->packageName()); - $this->addIfSet($artifacts, self::ARTIFACT_ASSET_PREVIEW, $this->assetPreview()); - $this->addIfSet( $artifacts, self::ARTIFACT_ASSET_NOTIFY, $this->assetNotify()); - } - - - $this->addIfSet($artifacts, self::ARTIFACT_VERSION, $this->version()); - $this->addIfSet($artifacts, self::ARTIFACT_CLOUD_WATCH, $this->cloudWatch()); - $this->addIfSet($artifacts, self::ARTIFACT_CONSOLE_TEXT, $this->consoleText()); - $this->addIfSet($artifacts, self::ARTIFACT_PUBLISH_PROPERTIES, $this->publishProperties()); - - // We need to at least have one artifact or the current Portal will fail to parse the JSON. - // TODO: Treat these like the others once Poral is fixed. - $artifacts[self::ARTIFACT_CLOUD_WATCH] = $this->cloudWatch(); - - return $artifacts; - } - - public function getLinks() - { - $links = []; - if($this->id){ - $links[Link::REL_SELF] = Url::toRoute(['/job/'.$this->job_id.'/build/'.$this->id], true); - $links['job'] = Url::toRoute(['/job/'.$this->job_id], true); - } - return $links; - } - - /** - * Check if the new status is a valid transition from current - * @param string $current The current status of Build - * @param string $new The desired status for Build - * @return bool - */ - public function isValidStatusTransition($new, $current = null) - { - $current = $current ?: $this->status; - if(in_array($new, $this->validStatusTransitions[$current])){ - return true; - } - - return false; - } - public function isValidChannelTransition($new, $current = null) - { - try { - $current = $current ?: $this->channel; - if (in_array($new, $this->validChannelTransitions[$current])){ - return true; - } - } - catch (\Exception $e) { - } - - return false; - } - public function jobName() - { - return $this->job->nameForBuild(); - } - - /** - * Returns the build specified by $build_id. The inclusion of the $job_id - * is done for validation purposes since this is also passed into the actions - * that use this method - * - * @param type $job_id - * @param type $build_id - * @return type Build - */ - public static function findOneById($job_id, $build_id) - { - $build = Build::findOne(['id' => $build_id, 'job_id' => $job_id]); - if ($build) { - if ($build->status == Build::STATUS_EXPIRED) { - $build = null; - } - } - return $build; - } - - public static function findOneByBuildId($build_id) - { - $build = Build::findOne(['id' => $build_id]); - if ($build) { - if ($build->status == Build::STATUS_EXPIRED) { - $build = null; - } - } - return $build; - } - /** - * Returns array of all non expired builds associated with the specified job - * - * @param type $job_id - * @return type array of Build - */ - public static function findAllActiveByJobId($job_id) - { - $builds = Build::find()->where('job_id = :job_id and status != :status', - ['job_id'=>$job_id, 'status'=>Build::STATUS_EXPIRED])->all(); - return $builds; - } - /** - * Returns array of all in progress builds associated with the specified job - * - * @param type $job_id - * @return type array of Build - */ - public static function findAllRunningByJobId($job_id) - { - $builds = Build::find()->where('job_id = :job_id and (status = :active or status = :postprocessing)', - ['job_id'=>$job_id, 'active'=>Build::STATUS_ACTIVE, 'postprocessing'=>Build::STATUS_POSTPROCESSING])->all(); - return $builds; - } - - /** - * Returns array of all builds associated with the specified job - * - * @param type $job_id - * @return type array of Build - */ - public static function findAllByJobId($job_id) - { - $builds = Build::find()->where('job_id = :job_id', - ['job_id'=>$job_id])->all(); - return $builds; - } - - /** - * Clears the artifacts for a build - */ - public function clearArtifacts() - { - $this->artifact_url_base = null; - $this->artifact_files = null; - $this->save(); - } - /** - * Check for dependent builds and delete them prior to deleting - * record - */ - public function beforeDelete() { - foreach (Release::findAllByBuildId($this->id) as $release) - { - if (!$release->delete()){ - return false; - } - } - return parent::beforeDelete(); - } - - public function artifactType($key) { - $type = "unknown"; - $path_parts = pathinfo($key); - $file = $path_parts['basename']; - // Files without extension need to be checked before access - // path_parts['extension'] - if ( $file == "cloudWatch") { - $type = self::ARTIFACT_CLOUD_WATCH; - } else if ($path_parts['extension'] === "log") { - $type = self::ARTIFACT_CONSOLE_TEXT; - } else if ($path_parts['extension'] === "aab") { - $type = self::ARTIFACT_AAB; - } else if ($path_parts['extension'] === "apk") { - $type = self::ARTIFACT_APK; - } else if ($file === "version_code.txt") { - $type = self::ARTIFACT_VERSION_CODE; - } else if ($file === "version.json") { - $type = self::ARTIFACT_VERSION; - } else if ($file === "package_name.txt") { - $type = self::ARTIFACT_PACKAGE_NAME; - } else if ($file === "publish-properties.json") { - $type = self::ARTIFACT_PUBLISH_PROPERTIES; - } else if ($file === "about.txt") { - $type = self::ARTIFACT_ABOUT; - } else if ($file === "whats_new.txt") { - $type = self::ARTIFACT_WHATS_NEW; - } else if ($file === "html.zip") { - $type = self::ARTIFACT_HTML; - } else if ($file === "pwa.zip") { - $type = self::ARTIFACT_PWA; - } else if ($file === "private_key.pepk") { - $type = self::ARTIFACT_ENCRYPTED_KEY; - } else if (preg_match("/asset-package\/.*\.zip$/", $key)) { - $type = self::ARTIFACT_ASSET_PACKAGE; - $file = "asset-package/" . $file; - } else if (preg_match("/asset-package\/preview\.html$/", $key)) { - $type = self::ARTIFACT_ASSET_PREVIEW; - $file = "asset-package/preview.html"; - } else if (preg_match("/asset-package\/notify\.json$/", $key)) { - $type = self::ARTIFACT_ASSET_NOTIFY; - $file = "asset-package/notify.json"; - } else if (preg_match("/play-listing\/index\.html$/", $key)) { - $type = self::ARTIFACT_PLAY_LISTING; - $file = "play-listing/index.html"; - } else if (preg_match("/play-listing\/manifest.json$/", $key)) { - $type = self::ARTIFACT_PLAY_LISTING_MANIFEST; - $file = "play-listing/manifest.json"; - } else if (preg_match("/data_safety\.csv$/", $key)) { - $type = self::ARTIFACT_DATA_SAFETY_CSV; - } else if (preg_match("/play-listing\.zip$/", $key)) { - $type = self::ARTIFACT_PLAY_LISTING_DOWNLOAD; - } - - return array($type, $file); - } - - private function appendArtifact($file) { - if (empty($this->artifact_files)) { - $this->artifact_files = $file; - } else { - $this->artifact_files .= "," . $file; - } - } - - public function beginArtifacts($baseUrl) { - $this->artifact_url_base = $baseUrl; - $this->artifact_files = null; - } - - public function handleArtifact($fileKey, $contents) { - list($type, $file) = $this->artifactType($fileKey); - switch ($type) { - case self::ARTIFACT_VERSION_CODE: - $this->version_code = $contents; - break; - - case self::ARTIFACT_VERSION: -// FUTURE: pull version_code from version.json (as versionCode property) -// $version = json_decode($contents); -// $this->version_code = $version['versionCode']; // NOT TESTED! -// break; - - case self::ARTIFACT_ABOUT: - case self::ARTIFACT_AAB: - case self::ARTIFACT_APK: - case self::ARTIFACT_HTML: - case self::ARTIFACT_PWA: - case self::ARTIFACT_PLAY_LISTING: - case self::ARTIFACT_PLAY_LISTING_MANIFEST: - case self::ARTIFACT_PACKAGE_NAME: - case self::ARTIFACT_PUBLISH_PROPERTIES: - case self::ARTIFACT_WHATS_NEW: - case self::ARTIFACT_CLOUD_WATCH: - case self::ARTIFACT_CONSOLE_TEXT: - case self::ARTIFACT_ENCRYPTED_KEY: - case self::ARTIFACT_ASSET_PREVIEW: - case self::ARTIFACT_ASSET_PACKAGE: - case self::ARTIFACT_ASSET_NOTIFY: - case self::ARTIFACT_DATA_SAFETY_CSV: - case self::ARTIFACT_PLAY_LISTING_DOWNLOAD: - break; - - default: - // Don't include in files - return; - } - $this->appendArtifact($file); - } - - public function getArtifactUrlBase() { - return $this->artifact_url_base; - } - - public function encodeFilename($filename) { - return str_replace('%2F', '/', rawurlencode($filename)); - } - - private function getArtifactUrl($pattern) { - $filename = $this->getArtifactFilename($pattern); - if (!empty($filename)) - { - return $this->artifact_url_base . $this->encodeFilename($filename); - } - return null; - } - private function getArtifactFilename($pattern) { - if (!empty($this->artifact_files)) { - $files = explode(",", $this->artifact_files); - foreach ($files as $file) { - if (preg_match($pattern, $file)) { - return $file; - } - } - } - return null; - } - private function getArtfactFilenameCount($pattern) { - if (!empty($this->artifact_files)) { - $files = explode(",", $this->artifact_files); - $count = 0; - foreach ($files as $file) { - if (preg_match($pattern, $file)) { - $count = $count + 1; - } - } - return $count; - } - return 0; - } - private function getArtifactUrls($pattern) { - $filenames = $this->getArtifactFilenames($pattern); - if (!empty($filenames)) - { - $urls = array(); - foreach ($filenames as $filename) { - array_push($urls, $this->artifact_url_base . $this->encodeFilename($filename)); - } - return $urls; - } - return null; - } - private function getArtifactFilenames($pattern) { - if (!empty($this->artifact_files)) { - $files = explode(",", $this->artifact_files); - $filenames = array(); - foreach ($files as $file) { - if (preg_match($pattern, $file)) { - array_push($filenames, $file); - } - } - return $filenames; - } - return null; - } - public function apkFilename() { - return $this->getArtifactFilename("/\.apk$/"); - } - public function apk() { - return $this->getArtifactUrl("/\.apk$/"); - } - public function apkCount() { - return $this->getArtfactFilenameCount("/\.apk$/"); - } - public function apks() { - return $this->getArtifactUrls("/\.apk$/"); - } - public function aab() { - return $this->getArtifactUrl("/\.aab$/"); - } - public function about() { - return $this->getArtifactUrl("/about\.txt$/"); - } - public function playListing() { - return $this->getArtifactUrl("/play-listing\/index\.html$/"); - } - public function playListingManifest() { - return $this->getArtifactUrl("/play-listing\/manifest\.json$/"); - } - public function playListingDownload() { - return $this->getArtifactUrl("/play-listing\.zip$/"); - } - public function versionCode() { - return $this->getArtifactUrl("/version_code\.txt$/"); - } - public function version() { - return $this->getArtifactUrl("/version\.json$/"); - } - public function packageName() { - return $this->getArtifactUrl("/package_name\.txt$/"); - } - public function publishProperties() { - return $this->getArtifactUrl("/publish-properties\.json$/"); - } - public function whatsNew() { - return $this->getArtifactUrl("/whats_new\.txt$/"); - } - public function html() { - return $this->getArtifactUrl("/html\.zip$/"); - } - public function pwa() { - return $this->getArtifactUrl("/pwa\.zip$/"); - } - public function cloudWatch() { - return $this->console_text_url; - } - public function consoleText() { - // We used to return consoleText as the first item that matched "\.log$" and we REALLY want console.log - // which is generated by BuildEngine. There is a APK_NAME-output.log which doesn't have everything in the log. - return $this->getArtifactUrl("/console\.log$/"); - } - public function encryptedKey() { - return $this->getArtifactUrl("/private_key\.pepk$/"); - } - public function assetPackage() { - return $this->getArtifactUrl("/asset-package\/.*\.zip$/"); - } - public function assetPreview() { - return $this->getArtifactUrl("/asset-package\/preview\.html$/"); - } - public function assetNotify() { - return $this->getArtifactUrl("/asset-package\/notify\.json$/"); - } - public function dataSafetyCsv() { - return $this->getArtifactUrl( "/data_safety\.csv$/"); - } - - /** - * - * get build details for logging. - * @param Build $build - * @return array - */ - public static function getlogBuildDetails($build) - { - $jobName = $build->job->name(); - $log = [ - 'jobName' => $jobName - ]; - $log['buildId'] = $build->id; - $log['buildStatus'] = $build->status; - $log['buildNumber'] = $build->build_guid; - $log['buildResult'] = $build->result; - if (!is_null($build->artifact_url_base)) { - $log['buildArtifactUrlBase'] = $build->artifact_url_base; - } - if (!is_null($build->artifact_files)) { - $log['buildArtifactFiles'] = $build->artifact_files; - } - - echo "Job=$jobName, Id=$build->id, Status=$build->status, Number=$build->build_guid, " - . "Result=$build->result, ArtifactUrlBase=$build->artifact_url_base, ArtifactFiles=$build->artifact_files". PHP_EOL; - return $log; - } - /** - * Gets the base prefix for the s3 within the bucket - * - * @param string $productStage - stg or prd - * @return string prefix - */ - public function getBasePrefixUrl($productStage) { - $artifactPath = S3::getArtifactPath($this->job, $productStage); - $buildNumber = (string)$this->id; - $repoUrl = $artifactPath . "/" . $buildNumber; - return $repoUrl; - } - -} diff --git a/application/common/models/BuildBase.php b/application/common/models/BuildBase.php deleted file mode 100644 index 367925eb..00000000 --- a/application/common/models/BuildBase.php +++ /dev/null @@ -1,97 +0,0 @@ - 255], - [['error', 'artifact_url_base'], 'string', 'max' => 2083], - [['artifact_files'], 'string', 'max' => 4096], - [['job_id'], 'exist', 'skipOnError' => true, 'targetClass' => Job::className(), 'targetAttribute' => ['job_id' => 'id']], - ]; - } - - /** - * {@inheritdoc} - */ - public function attributeLabels() - { - return [ - 'id' => Yii::t('app', 'ID'), - 'job_id' => Yii::t('app', 'Job ID'), - 'status' => Yii::t('app', 'Status'), - 'result' => Yii::t('app', 'Result'), - 'error' => Yii::t('app', 'Error'), - 'created' => Yii::t('app', 'Created'), - 'updated' => Yii::t('app', 'Updated'), - 'channel' => Yii::t('app', 'Channel'), - 'version_code' => Yii::t('app', 'Version Code'), - 'artifact_url_base' => Yii::t('app', 'Artifact Url Base'), - 'artifact_files' => Yii::t('app', 'Artifact Files'), - 'build_guid' => Yii::t('app', 'Build Guid'), - 'console_text_url' => Yii::t('app', 'Console Text Url'), - 'codebuild_url' => Yii::t('app', 'Codebuild Url'), - 'targets' => Yii::t('app', 'Targets'), - 'environment' => Yii::t('app', 'Environment'), - ]; - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getJob() - { - return $this->hasOne(Job::className(), ['id' => 'job_id']); - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getReleases() - { - return $this->hasMany(Release::className(), ['build_id' => 'id']); - } -} diff --git a/application/common/models/Client.php b/application/common/models/Client.php deleted file mode 100644 index 54b5a364..00000000 --- a/application/common/models/Client.php +++ /dev/null @@ -1,56 +0,0 @@ - $token]); - } - - public function scenarios() - { - return ArrayHelper::merge(parent::scenarios(),[ - - ]); - } - - public function rules() - { - return ArrayHelper::merge(parent::rules(),[ - [ - ['created','updated'],'default', 'value' => Utils::getDatetime(), - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - [ - ['prefix'], 'match', 'pattern'=>'/^([a-zA-Z0-9])+$/', - ], - ]); - } - - /** - * Check for dependent jobs and delete them prior to deleting - * record - */ - public function beforeDelete() { - foreach (Job::findAllByClientId($this->id) as $job) - { - if (!$job->delete()){ - return false; - } - } - return parent::beforeDelete(); - } -} diff --git a/application/common/models/ClientBase.php b/application/common/models/ClientBase.php deleted file mode 100644 index 0935a7ef..00000000 --- a/application/common/models/ClientBase.php +++ /dev/null @@ -1,71 +0,0 @@ - 255], - [['prefix'], 'string', 'max' => 4], - ]; - } - - /** - * {@inheritdoc} - */ - public function attributeLabels() - { - return [ - 'id' => Yii::t('app', 'ID'), - 'access_token' => Yii::t('app', 'Access Token'), - 'prefix' => Yii::t('app', 'Prefix'), - 'created' => Yii::t('app', 'Created'), - 'updated' => Yii::t('app', 'Updated'), - ]; - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getJobs() - { - return $this->hasMany(Job::className(), ['client_id' => 'id']); - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getProjects() - { - return $this->hasMany(Project::className(), ['client_id' => 'id']); - } -} diff --git a/application/common/models/EmailQueue.php b/application/common/models/EmailQueue.php deleted file mode 100644 index 3c76c05d..00000000 --- a/application/common/models/EmailQueue.php +++ /dev/null @@ -1,35 +0,0 @@ - Utils::getDatetime(), - ], - ]); - } - -} \ No newline at end of file diff --git a/application/common/models/EmailQueueBase.php b/application/common/models/EmailQueueBase.php deleted file mode 100644 index 2e37d8b7..00000000 --- a/application/common/models/EmailQueueBase.php +++ /dev/null @@ -1,65 +0,0 @@ - 255], - ]; - } - - /** - * {@inheritdoc} - */ - public function attributeLabels() - { - return [ - 'id' => Yii::t('app', 'ID'), - 'to' => Yii::t('app', 'To'), - 'cc' => Yii::t('app', 'Cc'), - 'bcc' => Yii::t('app', 'Bcc'), - 'subject' => Yii::t('app', 'Subject'), - 'text_body' => Yii::t('app', 'Text Body'), - 'html_body' => Yii::t('app', 'Html Body'), - 'attempts_count' => Yii::t('app', 'Attempts Count'), - 'last_attempt' => Yii::t('app', 'Last Attempt'), - 'created' => Yii::t('app', 'Created'), - 'error' => Yii::t('app', 'Error'), - ]; - } -} diff --git a/application/common/models/Job.php b/application/common/models/Job.php deleted file mode 100644 index c6f1bafb..00000000 --- a/application/common/models/Job.php +++ /dev/null @@ -1,266 +0,0 @@ - Utils::getDatetime(), - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - [ - ['request_id'], 'unique', - ], - [ - // This should come from another model - // 'app_id', 'exist', 'targetClass' => 'common\models\App', 'targetAttribute' => 'id', - // message => \Yii::t('app', 'Invalid App ID'), - 'app_id', 'in', 'range' => [Job::APP_TYPE_SCRIPTUREAPP, Job::APP_TYPE_READINGAPP, Job::APP_TYPE_DICTIONARYAPP, Job::APP_TYPE_KEYBOARDAPP], - ], - // The currently supported Git Urls are for AWS Codecommit - //[ - // 'git_url', 'url', - // 'pattern' => '/^ssh:\/\/(\w+@)?git-codecommit\./', - // 'message' => \Yii::t('app', 'Git SSH Url is required.') - //], - [ - ['client_id'],'default', 'value' => function() { - return self::getCurrentClientId(); - }, - ], - ]); - } - - public function fields() - { - return [ - 'id', - 'request_id', - 'git_url', - 'app_id', - 'publisher_id', - 'created' => function(){ - return Utils::getIso8601($this->created); - }, - 'updated' => function(){ - return Utils::getIso8601($this->updated); - }, - ]; - } - - public function getLinks() - { - $links = []; - if($this->id){ - $links[Link::REL_SELF] = Url::toRoute(['/job/'.$this->id], true); - } - - return $links; - } - - /** - * Get the most recent build (by date created) - * @return Build - */ - public function getLatestBuild() - { - return Build::find() - ->where(['job_id' => $this->id]) - ->orderBy('created DESC') - ->one(); - } - - /** - * - * @return Build - */ - public function createBuild($targets, $environment) - { - $build = new Build(); - $build->job_id = $this->id; - $build->targets = $targets; - $build->environment = $environment; - $build->save(); - - return $build; - } - - /** - * Return the name of the job - * @return string - */ - public function name() - { - $client = $this->getLinkedClient(); - if (!is_null($client)) { - return $this->app_id."_".$client->prefix."_".$this->id; - } else { - return $this->app_id."_".$this->id; - } - } - - /** - * Returns array of all jobs associated with the specified client - * - * @param integer $client_id - * @return array array of Build - */ - public static function findAllByClientId($client_id) - { - if ($client_id) { - $jobs = Job::find()->where('client_id = :client_id', - ['client_id'=>$client_id])->all(); - return $jobs; - } else { - /* No way I could find to find records where the - * field is equal to null. Always returned nothing - */ - $jobs = []; - foreach (Job::find()->each(50) as $job) - { - if ($job->client_id == null) { - $jobs[] = $job; - } - } - return $jobs; - } - } - /** - * Return the nae of the job to use when publishing - * @return String - */ - public function nameForPublish() - { - return "publish_".$this->name(); - } - - /** - * Return the nae of the job to use when publishing - * @return String - */ - public function nameForBuild() - { - return "build_".$this->name(); - } - - /** - * Returns the name of the code build process to run - * @return String - */ - public function nameForBuildProcess() - { - return "build_".$this->app_id; - } - /** - * Returns the name of the code build process to run - * @return String - */ - public function nameForPublishProcess() - { - return "publish_".$this->app_id; - } - - /** - * Convenience method to find the job by Id - * @param integer $id - * @return Job Job - */ - public static function findById($id) - { - return self::findOne(['id' => $id]); - } - public static function findByIdFiltered($id) - { - $job = self::findById($id); - if (!is_null($job)){ - if ($job->client_id != self::getCurrentClientId()) { - $job = null; - } - } - return $job; - } - /** - * Create an entry containing the name of all - * build and publish jobs that are valid based on the - * current database contents - * @return array of Strings - */ - public static function getJobNames() - { - $jobs = []; - foreach (Job::find()->each(50) as $job) - { - $jobs[$job->nameForBuild()] = 1; - $jobs[$job->nameForPublish()] = 1; - } - return $jobs; - } - - /** - * Check for dependent builds and delete them prior to deleting - * record - */ - public function beforeDelete() { - foreach (Build::findAllByJobId($this->id) as $build) - { - if (!$build->delete()){ - return false; - } - } - return parent::beforeDelete(); - } - public static function getCurrentClientId() { - $cid = ""; - $user = Utils::getCurrentUser(); - if (!is_null($user)) { - $cid = $user->getClientId(); - } - return $cid; - } - public function getLinkedClient() - { - if (is_null($this->client_id)) { - return null; - } - return Client::findOne(['id' => $this->client_id]); - } - public static function recordCount() { - $jobs = Job::find()->all(); - $count = count($jobs); - return $count; - } -} diff --git a/application/common/models/JobBase.php b/application/common/models/JobBase.php deleted file mode 100644 index 370aa840..00000000 --- a/application/common/models/JobBase.php +++ /dev/null @@ -1,86 +0,0 @@ - 255], - [['git_url'], 'string', 'max' => 2083], - [['jenkins_build_url', 'jenkins_publish_url'], 'string', 'max' => 1024], - [['client_id'], 'exist', 'skipOnError' => true, 'targetClass' => Client::className(), 'targetAttribute' => ['client_id' => 'id']], - ]; - } - - /** - * {@inheritdoc} - */ - public function attributeLabels() - { - return [ - 'id' => Yii::t('app', 'ID'), - 'request_id' => Yii::t('app', 'Request ID'), - 'git_url' => Yii::t('app', 'Git Url'), - 'app_id' => Yii::t('app', 'App ID'), - 'publisher_id' => Yii::t('app', 'Publisher ID'), - 'created' => Yii::t('app', 'Created'), - 'updated' => Yii::t('app', 'Updated'), - 'client_id' => Yii::t('app', 'Client ID'), - 'existing_version_code' => Yii::t('app', 'Existing Version Code'), - 'jenkins_build_url' => Yii::t('app', 'Jenkins Build Url'), - 'jenkins_publish_url' => Yii::t('app', 'Jenkins Publish Url'), - ]; - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getBuilds() - { - return $this->hasMany(Build::className(), ['job_id' => 'id']); - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getClient() - { - return $this->hasOne(Client::className(), ['id' => 'client_id']); - } -} diff --git a/application/common/models/LoginForm.php b/application/common/models/LoginForm.php deleted file mode 100644 index 6ec31f21..00000000 --- a/application/common/models/LoginForm.php +++ /dev/null @@ -1,78 +0,0 @@ -hasErrors()) { - $user = $this->getUser(); - if (!$user || !$user->validatePassword($this->password)) { - $this->addError($attribute, 'Incorrect username or password.'); - } - } - } - - /** - * Logs in a user using the provided username and password. - * - * @return boolean whether the user is logged in successfully - */ - public function login() - { - if ($this->validate()) { - return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0); - } else { - return false; - } - } - - /** - * Finds user by [[username]] - * - * @return User|null - */ - public function getUser() - { - if ($this->_user === false) { - $this->_user = User::findByUsername($this->username); - } - - return $this->_user; - } -} diff --git a/application/common/models/OperationQueue.php b/application/common/models/OperationQueue.php deleted file mode 100644 index ebbca533..00000000 --- a/application/common/models/OperationQueue.php +++ /dev/null @@ -1,205 +0,0 @@ - Utils::getDatetime(), - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - ]); - } - public static function findOrCreate($operation, $id, $parms) { - $job = self::findOne([ - 'operation' => $operation, - 'operation_object_id' => $id - ]); - if(!$job){ - $job = self::createOperation($operation, $id, $parms); - return; - } else { - // If another identical entry is requested, reset the timer - // and retry counts - $job->attempt_count = 0; - $job->try_after = Utils::getDatetime(); - if (!$job->save()) { - echo "Failed to save operation for task $operation" . PHP_EOL; - } - } - return $job; - } - public static function createOperation($operation, $objectID, $parms) - { - $job = new OperationQueue(); - $job->operation = $operation; - $job->operation_object_id = $objectID; - $job->operation_parms = $parms; - $job->attempt_count = 0; - $job->try_after = Utils::getDatetime(); - if (!$job->save()) { - echo "Failed to create operation for task $operation" . PHP_EOL; - } - - return $job; - } - public static function createOperationObject($operation, $id, $operation_parms) { - $operationObject = null; - switch($operation){ - case OperationQueue::SAVETOS3: - $operationObject = new CopyToS3Operation($id, $operation_parms); - break; - case OperationQueue::UPDATEPROJECT: - $operationObject = new ProjectUpdateOperation($id, $operation_parms); - break; - case OperationQueue::SAVEERRORTOS3: - $operationObject = new CopyErrorToS3Operation($id, $operation_parms); - } - return $operationObject; - } - /** - * Find the next job in queue and process it if not over max attempts - * @param string|null $currentTime The datetime after which to try jobs - * @return bool|null - * @throws \Exception - */ - public static function processNext($currentTime=null) - { - /** - * Get the next job in queue - */ - $currentTime = $currentTime ?: Utils::getDatetime(); - /* @var $job OperationQueue */ - $job = OperationQueue::find() - ->where(['start_time' => null]) - ->andWhere('`try_after` <= :now',[':now' => $currentTime]) - ->orderBy('try_after',SORT_ASC) - ->one(); - /** - * If there are no jobs ready in queue, return null - */ - if(!$job){ - return false; - } - $object_id = $job->operation_object_id; - $operationObject = self::createOperationObject($job->operation,$object_id, $job->operation_parms); - $maxDelay = $operationObject->getMaximumDelay(); - $maxAttempts = $operationObject->getMaximumRetries(); - $alertAfterAttemptCount = $operationObject->getAlertAfterAttemptCount(); - /** - * Make sure we're not over the limit of max_attempts before proceeding - */ - if($job->attempt_count > $maxAttempts){ - $message = "Operation Queue entry beyond max attempts. ID: ".$job->id.", " - ."for task: ".$job->operation; - $job->try_after = Utils::getDatetime(self::getNextTryTime($job->attempt_count, false, $maxAttempts, $maxDelay)); - $job->save(); - throw new MaxRetriesExceededException($message, 1457104373); - } - /** - * Process operation - * Catch exceptions to increment attempt_count and such - */ - try{ - $operationObject->performOperation(); - } catch (\Exception $e) { - /** - * Log exception - */ - \Yii::error('cron=operation-queue exceptionId='.$e->getCode().' message='.$e->getMessage(),'cron'); - - /** - * Update job - */ - try{ - $job->attempt_count++; - $job->last_attempt = Utils::getDatetime(); - $job->start_time = null; - $job->last_error = $e->getMessage(); - $job->try_after = Utils::getDatetime(self::getNextTryTime($job->attempt_count, false, $maxAttempts, $maxDelay)); - if(!$job->save()){ - throw new \Exception("Unable to update OperationQueue entry, model errors: ".print_r($job->getFirstErrors(),true),1457104658); - } - } catch (\Exception $e) { - \Yii::error('cron=operation-queue exceptionId='.$e->getCode().' message='.$e->getMessage(),'cron'); - } - - /** - * Check if we need to send an alert - */ - if($job->attempt_count > $alertAfterAttemptCount){ - $to = EmailUtils::getAdminEmailAddress(); - $subject = "OperationQueue entry failed more than limit"; - $body = "OperationQueue ID: ".$job->id."
    ".PHP_EOL; - $body .= "Activity: ".$job->operation."
    ".PHP_EOL; - $body .= "Failure Count: ".$job->attempt_count."
    ".PHP_EOL; - $body .= "Failed Since: ".$job->created." GMT
    ".PHP_EOL; - $body .= "Exception: ".$e->getMessage(); - $email = new EmailQueue(); - $email->to = $to; - $email->subject = $subject; - $email->html_body = $body; - try{ - $email->save(); - } catch (\Exception $ex){ - \Yii::error('cron=operation-queue EmailAlertError=OperationQueue entry failed more than limit message='.$body,'cron'); - } - } - - throw $e; - } - /** - * Processing step complete, delete queue entry - */ - $job->delete(); - - return true; - } - /** - * Calculate delay factor based on number of attempts and return - * as datetime based on time provided (optional) - * @param int $attempts - * @param int|bool $time - * @param int $maxAttempts - * @param int $maxDelay - * @return int - */ - public static function getNextTryTime($attempts, $time, $maxAttempts, $maxDelay) - { - $time = $time ?: time(); - if($attempts > $maxAttempts){ - \Yii::warning("OperationQueue: Have been trying operation for over 1 week", "application"); - return $time * 2; // set to way in the future so it won't try again - } - $delay = (int) pow($attempts, 3); - if($delay > $maxDelay){ - $delay = $maxDelay; - } - return $time + ($delay * 60); - } -} diff --git a/application/common/models/OperationQueueBase.php b/application/common/models/OperationQueueBase.php deleted file mode 100644 index 2df82392..00000000 --- a/application/common/models/OperationQueueBase.php +++ /dev/null @@ -1,65 +0,0 @@ - 255], - [['operation_parms', 'last_error'], 'string', 'max' => 2048], - ]; - } - - /** - * {@inheritdoc} - */ - public function attributeLabels() - { - return [ - 'id' => Yii::t('app', 'ID'), - 'operation' => Yii::t('app', 'Operation'), - 'operation_object_id' => Yii::t('app', 'Operation Object ID'), - 'operation_parms' => Yii::t('app', 'Operation Parms'), - 'attempt_count' => Yii::t('app', 'Attempt Count'), - 'last_attempt' => Yii::t('app', 'Last Attempt'), - 'try_after' => Yii::t('app', 'Try After'), - 'start_time' => Yii::t('app', 'Start Time'), - 'last_error' => Yii::t('app', 'Last Error'), - 'created' => Yii::t('app', 'Created'), - 'updated' => Yii::t('app', 'Updated'), - ]; - } -} diff --git a/application/common/models/OriginalUser.php b/application/common/models/OriginalUser.php deleted file mode 100644 index d51d7ec8..00000000 --- a/application/common/models/OriginalUser.php +++ /dev/null @@ -1,188 +0,0 @@ - self::STATUS_ACTIVE], - ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]], - ]; - } - - /** - * @inheritdoc - */ - public static function findIdentity($id) - { - return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]); - } - - /** - * @inheritdoc - */ - public static function findIdentityByAccessToken($token, $type = null) - { - throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); - } - - /** - * Finds user by username - * - * @param string $username - * @return static|null - */ - public static function findByUsername($username) - { - return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]); - } - - /** - * Finds user by password reset token - * - * @param string $token password reset token - * @return static|null - */ - public static function findByPasswordResetToken($token) - { - if (!static::isPasswordResetTokenValid($token)) { - return null; - } - - return static::findOne([ - 'password_reset_token' => $token, - 'status' => self::STATUS_ACTIVE, - ]); - } - - /** - * Finds out if password reset token is valid - * - * @param string $token password reset token - * @return boolean - */ - public static function isPasswordResetTokenValid($token) - { - if (empty($token)) { - return false; - } - $expire = Yii::$app->params['user.passwordResetTokenExpire']; - $parts = explode('_', $token); - $timestamp = (int) end($parts); - return $timestamp + $expire >= time(); - } - - /** - * @inheritdoc - */ - public function getId() - { - return $this->getPrimaryKey(); - } - - /** - * @inheritdoc - */ - public function getAuthKey() - { - return $this->auth_key; - } - - /** - * @inheritdoc - */ - public function validateAuthKey($authKey) - { - return $this->getAuthKey() === $authKey; - } - - /** - * Validates password - * - * @param string $password password to validate - * @return boolean if password provided is valid for current user - */ - public function validatePassword($password) - { - return Yii::$app->security->validatePassword($password, $this->password_hash); - } - - /** - * Generates password hash from password and sets it to the model - * - * @param string $password - */ - public function setPassword($password) - { - $this->password_hash = Yii::$app->security->generatePasswordHash($password); - } - - /** - * Generates "remember me" authentication key - */ - public function generateAuthKey() - { - $this->auth_key = Yii::$app->security->generateRandomString(); - } - - /** - * Generates new password reset token - */ - public function generatePasswordResetToken() - { - $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); - } - - /** - * Removes password reset token - */ - public function removePasswordResetToken() - { - $this->password_reset_token = null; - } -} diff --git a/application/common/models/Project.php b/application/common/models/Project.php deleted file mode 100644 index 8a3befbf..00000000 --- a/application/common/models/Project.php +++ /dev/null @@ -1,248 +0,0 @@ - [ - self::STATUS_ACTIVE, - self::STATUS_DELETE_PENDING, - ], - self::STATUS_ACTIVE => [ - self::STATUS_COMPLETED, - self::STATUS_DELETE_PENDING, - ], - self::STATUS_COMPLETED => [ - self::STATUS_DELETE_PENDING, - ], - self::STATUS_DELETE_PENDING => [ - self::STATUS_DELETING, - ], - self::STATUS_DELETING => [ - self::STATUS_COMPLETED, - ] - ]; - - public function scenarios() - { - return ArrayHelper::merge(parent::scenarios(),[ - - ]); - } - - public function rules() - { - return ArrayHelper::merge(parent::rules(),[ - [ - ['created','updated'],'default', 'value' => Utils::getDatetime(), - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - [ - 'status', 'in', 'range' => [ - self::STATUS_ACTIVE, - self::STATUS_COMPLETED, - self::STATUS_INITIALIZED, - self::STATUS_DELETE_PENDING, - self::STATUS_DELETING, - ], - ], - [ - 'status', 'default', 'value' => self::STATUS_INITIALIZED, - ], - [ - // This should come from another model - // 'app_id', 'exist', 'targetClass' => 'common\models\App', 'targetAttribute' => 'id', - // message => \Yii::t('app', 'Invalid App ID'), - 'app_id', 'in', 'range' => [Job::APP_TYPE_SCRIPTUREAPP, Job::APP_TYPE_READINGAPP, Job::APP_TYPE_DICTIONARYAPP, Job::APP_TYPE_KEYBOARDAPP], - ], - [ - ['client_id'],'default', 'value' => function() { - return self::getCurrentClientId(); - }, - ], - ]); - } - public function fields() - { - return [ - 'id', - 'status', - 'result', - 'error', - 'url', - 'user_id', - 'group_id', - 'app_id', - 'project_name', - 'language_code', - 'publishing_key', - 'created' => function(){ - return Utils::getIso8601($this->created); - }, - 'updated' => function(){ - return Utils::getIso8601($this->updated); - }, - ]; - } - public function getLinks() - { - $links = []; - if($this->id){ - $links[Link::REL_SELF] = Url::toRoute(['/project/'.$this->id], true); - } - - return $links; - } - public function groupName() - { - $groupName = 'CodeCommit-'.$this->entityName(); - return $groupName; - } - public function entityName() - { - $entityName = strtoupper($this->group_id); - $client = $this->getLinkedClient(); - if (!is_null($client)) { - $entityName = $client->prefix."-".$entityName; - } - return $entityName; - } - public function getLinkedClient() - { - if (is_null($this->client_id)) { - return null; - } - return Client::findOne(['id' => $this->client_id]); - } - - function repoName() - { - $repoName = $this->app_id.'-'.$this->entityName().'-'.$this->language_code . '-' . $this->project_name; - - $repoName = Utils::lettersNumbersHyphensOnly($repoName); - - return $repoName; - } - - /** - * @return string - */ - public function getS3Bucket() - { - return str_replace('"', "", S3::getProjectsBucket()); - } - private function getS3BaseFolder() - { - $s3folder = $this->language_code.'-'.$this->id.'-'.$this->project_name; - return $s3folder; - } - private function getS3Folder() - { - $client = $this->getLinkedClient(); - $s3client = (is_null($client)) ? "" : $client->prefix . '/'; - $s3app = $this->app_id.'/'; - $s3folder = $this->getS3BaseFolder(); - return $s3client . $s3app . Utils::lettersNumbersHyphensOnly($s3folder); - - } - private function getS3Path() - { - $s3path = "s3://". $this->getS3Bucket() .'/' . $this->getS3Folder(); - - return $s3path; - } - public function setS3Project() { - $this->url = $this->getS3Path(); - $this->status = self::STATUS_COMPLETED; - $this->result = self::RESULT_SUCCESS; - } - - public function isS3Project() { - return (substr($this->url, 0, 5) === "s3://"); - } - - public function getS3ProjectPath() { - return (substr($this->url, 5)); - } - - /** - * Returns array of all projects associated with the specified client - * - * @param integer $client_id - * @return array array of Build - */ - public static function findAllByClientId($client_id) - { - if ($client_id) { - $projects = Project::find()->where('client_id = :client_id', - ['client_id'=>$client_id])->all(); - return $projects; - } else { - /* No way I could find to find records where the - * field is equal to null. Always returned nothing - */ - $projects = []; - foreach (Project::find()->each(50) as $project) - { - if ($project->client_id == null) { - $projects[] = $project; - } - } - return $projects; - } - } - public static function findById($id) - { - return self::findOne(['id' => $id]); - } - public static function findByIdFiltered($id) - { - $project = self::findById($id); - if (!is_null($project)){ - if ($project->client_id != self::getCurrentClientId()) { - $project = null; - } - } - return $project; - } - public static function getCurrentClientId() { - $cid = ""; - $user = Utils::getCurrentUser(); - if (!is_null($user)) { - $cid = $user->getClientId(); - } - return $cid; - } - -} \ No newline at end of file diff --git a/application/common/models/ProjectBase.php b/application/common/models/ProjectBase.php deleted file mode 100644 index 7a6d60de..00000000 --- a/application/common/models/ProjectBase.php +++ /dev/null @@ -1,82 +0,0 @@ - 255], - [['error'], 'string', 'max' => 2083], - [['url', 'publishing_key'], 'string', 'max' => 1024], - [['client_id'], 'exist', 'skipOnError' => true, 'targetClass' => Client::className(), 'targetAttribute' => ['client_id' => 'id']], - ]; - } - - /** - * {@inheritdoc} - */ - public function attributeLabels() - { - return [ - 'id' => Yii::t('app', 'ID'), - 'status' => Yii::t('app', 'Status'), - 'result' => Yii::t('app', 'Result'), - 'error' => Yii::t('app', 'Error'), - 'url' => Yii::t('app', 'Url'), - 'user_id' => Yii::t('app', 'User ID'), - 'group_id' => Yii::t('app', 'Group ID'), - 'app_id' => Yii::t('app', 'App ID'), - 'project_name' => Yii::t('app', 'Project Name'), - 'language_code' => Yii::t('app', 'Language Code'), - 'publishing_key' => Yii::t('app', 'Publishing Key'), - 'created' => Yii::t('app', 'Created'), - 'updated' => Yii::t('app', 'Updated'), - 'client_id' => Yii::t('app', 'Client ID'), - ]; - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getClient() - { - return $this->hasOne(Client::className(), ['id' => 'client_id']); - } -} diff --git a/application/common/models/Release.php b/application/common/models/Release.php deleted file mode 100644 index a2ea7f62..00000000 --- a/application/common/models/Release.php +++ /dev/null @@ -1,282 +0,0 @@ - [ - self::STATUS_ACCEPTED, - ], - self::STATUS_ACCEPTED => [ - self::STATUS_ACTIVE, - ], - self::STATUS_ACTIVE => [ - self::STATUS_COMPLETED, - self::STATUS_EXPIRED, - ], - self::STATUS_EXPIRED => [ - self::STATUS_ACTIVE, - ], - ]; - - const CHANNEL_ALPHA = 'alpha'; - const CHANNEL_BETA = 'beta'; - const CHANNEL_PRODUCTION = 'production'; - - public function scenarios() - { - return ArrayHelper::merge(parent::scenarios(),[ - - ]); - } - - public function rules() - { - return ArrayHelper::merge(parent::rules(),[ - [ - ['created','updated'],'default', 'value' => Utils::getDatetime(), - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - [ - 'status', 'in', 'range' => [ - self::STATUS_ACTIVE, - self::STATUS_ACCEPTED, - self::STATUS_COMPLETED, - self::STATUS_EXPIRED, - self::STATUS_INITIALIZED, - self::STATUS_POSTPROCESSING, - ], - ], - [ - 'status', 'default', 'value' => self::STATUS_INITIALIZED, - ], - [ - 'build_id', 'exist', 'targetClass' => 'common\models\Build', 'targetAttribute' => 'id', - 'message' => \Yii::t('app', 'Invalid Build ID'), - ], - [ - 'updated', 'default', 'value' => Utils::getDatetime(), 'isEmpty' => function(){ - // always return true so it get set on every save - return true; - }, - ], - [ - 'channel', 'in', 'range' => [ - self::CHANNEL_ALPHA, - self::CHANNEL_BETA, - self::CHANNEL_PRODUCTION, - ], - ] - ]); - } - public function fields() - { - return [ - 'id', - 'build_id', - 'status', - 'result', - 'error', - 'title', - 'defaultLanguage', - 'channel', - 'artifacts' => function() { - return [ - self::ARTIFACT_CLOUD_WATCH => $this->cloudWatch(), - self::ARTIFACT_CONSOLE_TEXT => $this->consoleText(), - self::ARTIFACT_PUBLISH_URL => $this->publishUrl()]; - }, - 'consoleText' => function(){ - return $this->consoleText(); - }, - 'created' => function(){ - return Utils::getIso8601($this->created); - }, - 'updated' => function(){ - return Utils::getIso8601($this->updated); - }, - ]; - } - - public function getLinks() - { - $links = []; - if($this->id){ - //$links[Link::REL_SELF] = Url::toRoute(['/release/'.$this->id], true); - } - return $links; - } - - /** - * Check if the new status is a valid transition from current - * @param string $current The current status of Build - * @param string $new The desired status for Build - * @return bool - */ - public function isValidStatusTransition($new, $current = null) - { - $current = $current ?: $this->status; - if(in_array($new, $this->validStatusTransitions[$current])){ - return true; - } - - return false; - } - - public function jobName() - { - return $this->build->job->nameForPublish(); - } - public function jobId() - { - return $this->build->job_id; - } - /** - * Returns array of all associated with the specified job - * - * @param type $build_id - * @return type array of Release - */ - public static function findAllByBuildId($build_id) - { - $releases = Release::find()->where('build_id = :build_id', - ['build_id'=>$build_id])->all(); - return $releases; - } - /** - * Clears the artifacts for a build - */ - public function clearArtifacts() - { - $this->artifact_url_base = null; - $this->artifact_files = null; - $this->save(); - } - private function getArtifactUrl($pattern) { - $filename = $this->getArtifactFilename($pattern); - if (!empty($filename)) - { - return $this->artifact_url_base . $filename; - } - return null; - } - private function getArtifactFilename($pattern) { - if (!empty($this->artifact_files)) { - $files = explode(",", $this->artifact_files); - foreach ($files as $file) { - if (preg_match($pattern, $file)) { - return $file; - } - } - } - return null; - } - private function appendArtifact($file) { - if (empty($this->artifact_files)) { - $this->artifact_files = $file; - } else { - $this->artifact_files .= "," . $file; - } - } - public function cloudWatch() { - return $this->console_text_url; - } - public function consoleText() { - return $this->getArtifactUrl("/\.log$/"); - } - - public function publishUrl() { - return $this->getArtifactUrl("/publish_url\.txt$/"); - } - /** - * Gets the base prefix for the s3 within the bucket for publish - * - * @param string $productStage - stg or prd - * @return string prefix - */ - public function getBasePrefixUrl($productStage) { - $artifactPath = S3::getArtifactPath($this->build->job, $productStage, true); - $buildNumber = (string)$this->id; - $repoUrl = $artifactPath . "/" . $buildNumber; - return $repoUrl; - } - public function beginArtifacts($baseUrl) { - $this->artifact_url_base = $baseUrl; - $this->artifact_files = null; - } - public function artifactType($key) { - $type = "unknown"; - $path_parts = pathinfo($key); - $file = $path_parts['basename']; - if ( $file == "cloudWatch") { - $type = self::ARTIFACT_CLOUD_WATCH; - } else if ($path_parts['extension'] === "log") { - $type = self::ARTIFACT_CONSOLE_TEXT; - } else if ($file === "publish_url.txt") { - $type = self::ARTIFACT_PUBLISH_URL; - } - - return array($type, $file); - } - - public function handleArtifact($fileKey, $contents) { - list($type, $file) = $this->artifactType($fileKey); - switch ($type) { - case self::ARTIFACT_CLOUD_WATCH: - case self::ARTIFACT_CONSOLE_TEXT: - case self::ARTIFACT_PUBLISH_URL: - break; - - default: - // Don't include in files - return; - } - $this->appendArtifact($file); - } - public static function findOneByBuildId($build_id) - { - $build = Release::findOne(['id' => $build_id]); - if ($build) { - if ($build->status == Release::STATUS_EXPIRED) { - $build = null; - } - } - return $build; - } - -} diff --git a/application/common/models/ReleaseBase.php b/application/common/models/ReleaseBase.php deleted file mode 100644 index 73dd5e81..00000000 --- a/application/common/models/ReleaseBase.php +++ /dev/null @@ -1,92 +0,0 @@ - 255], - [['error'], 'string', 'max' => 2083], - [['title'], 'string', 'max' => 30], - [['build_id'], 'exist', 'skipOnError' => true, 'targetClass' => Build::className(), 'targetAttribute' => ['build_id' => 'id']], - ]; - } - - /** - * {@inheritdoc} - */ - public function attributeLabels() - { - return [ - 'id' => Yii::t('app', 'ID'), - 'build_id' => Yii::t('app', 'Build ID'), - 'status' => Yii::t('app', 'Status'), - 'created' => Yii::t('app', 'Created'), - 'updated' => Yii::t('app', 'Updated'), - 'result' => Yii::t('app', 'Result'), - 'error' => Yii::t('app', 'Error'), - 'channel' => Yii::t('app', 'Channel'), - 'title' => Yii::t('app', 'Title'), - 'defaultLanguage' => Yii::t('app', 'Default Language'), - 'promote_from' => Yii::t('app', 'Promote From'), - 'build_guid' => Yii::t('app', 'Build Guid'), - 'console_text_url' => Yii::t('app', 'Console Text Url'), - 'codebuild_url' => Yii::t('app', 'Codebuild Url'), - 'targets' => Yii::t('app', 'Targets'), - 'environment' => Yii::t('app', 'Environment'), - 'artifact_url_base' => Yii::t('app', 'Artifact Url Base'), - 'artifact_files' => Yii::t('app', 'Artifact Files'), - ]; - } - - /** - * @return \yii\db\ActiveQuery - */ - public function getBuild() - { - return $this->hasOne(Build::className(), ['id' => 'build_id']); - } -} diff --git a/application/common/models/User.php b/application/common/models/User.php deleted file mode 100644 index 6b315d60..00000000 --- a/application/common/models/User.php +++ /dev/null @@ -1,50 +0,0 @@ -client_id; - } -} \ No newline at end of file diff --git a/application/common/preview/playlisting/index.html b/application/common/preview/playlisting/index.html deleted file mode 100644 index bc16d131..00000000 --- a/application/common/preview/playlisting/index.html +++ /dev/null @@ -1,548 +0,0 @@ - - - - - - - - - Play Store Overview - - - - - -
    - - -

    Default Language:

    -
    - -

    -
    -
    -
    - -
    -
    -
    -

    App Name

    -
    -
    -
    -
    -
    - - -
    -
    -
    - -
    -
    - - - - - -
    -
    -
    -
    - -
    - -
    - -
    -

    some description
    (select a language from top of the page)

    -
    - -
    -

    what's new
    (select a language from top of the page)

    -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    App Name
    -
    -
    - -
    -
    -
    -
    -

    some description
    (select a language from top of the page)

    - READ MORE -
    - -
    -
    -
    -
    - -

    -
    -
    -
    -

    some description
    (select a language from top of the page)

    -
    -
    -

    some description
    (select a language from top of the page)

    -
    -
    -

    what's new
    (select a language from top of the page)

    -
    -
    -
    - - diff --git a/application/composer.json b/application/composer.json deleted file mode 100644 index 041813b6..00000000 --- a/application/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "minimum-stability": "stable", - "require": { - "php": ">=7.4.0", - "yiisoft/yii2": "^2.0.15", - "yiisoft/yii2-gii": "*", - "yiisoft/yii2-bootstrap": "*", - "yiisoft/yii2-swiftmailer": "*", - "aws/aws-sdk-php": "^3.56", - "silinternational/yii2-jsonsyslog": "*" - }, - "require-dev": { - "yiisoft/yii2-codeception": "*", - "yiisoft/yii2-debug": "*", - "yiisoft/yii2-faker": "*", - "fillup/phpmyadmin-minimal": "4.2.2", - "codeception/codeception": "^2.0", - "codeception/specify": "0.*", - "codeception/verify": "0.*" - }, - "config": { - "process-timeout": 1800, - "allow-plugins": { - "yiisoft/yii2-composer": true - } - }, - "repositories": [ - { - "type": "composer", - "url": "https://asset-packagist.org" - } - ] -} diff --git a/application/composer.lock b/application/composer.lock deleted file mode 100644 index d8427d35..00000000 --- a/application/composer.lock +++ /dev/null @@ -1,5613 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "403373b5f130bd624ab75b837733fcc7", - "packages": [ - { - "name": "aws/aws-crt-php", - "version": "v1.2.4", - "source": { - "type": "git", - "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2", - "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "AWS SDK Common Runtime Team", - "email": "aws-sdk-common-runtime@amazon.com" - } - ], - "description": "AWS Common Runtime for PHP", - "homepage": "https://github.com/awslabs/aws-crt-php", - "keywords": [ - "amazon", - "aws", - "crt", - "sdk" - ], - "support": { - "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4" - }, - "time": "2023-11-08T00:42:13+00:00" - }, - { - "name": "aws/aws-sdk-php", - "version": "3.297.0", - "source": { - "type": "git", - "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "ad1f7be78d74d48628a6fe345818ce53bae64169" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ad1f7be78d74d48628a6fe345818ce53bae64169", - "reference": "ad1f7be78d74d48628a6fe345818ce53bae64169", - "shasum": "" - }, - "require": { - "aws/aws-crt-php": "^1.2.3", - "ext-json": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", - "mtdowling/jmespath.php": "^2.6", - "php": ">=7.2.5", - "psr/http-message": "^1.0 || ^2.0" - }, - "require-dev": { - "andrewsville/php-token-reflection": "^1.4", - "aws/aws-php-sns-message-validator": "~1.0", - "behat/behat": "~3.0", - "composer/composer": "^1.10.22", - "dms/phpunit-arraysubset-asserts": "^0.4.0", - "doctrine/cache": "~1.4", - "ext-dom": "*", - "ext-openssl": "*", - "ext-pcntl": "*", - "ext-sockets": "*", - "nette/neon": "^2.3", - "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3 || ^4.0", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", - "doctrine/cache": "To use the DoctrineCacheAdapter", - "ext-curl": "To send requests using cURL", - "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", - "ext-sockets": "To use client-side monitoring" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Aws\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" - } - ], - "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", - "keywords": [ - "amazon", - "aws", - "cloud", - "dynamodb", - "ec2", - "glacier", - "s3", - "sdk" - ], - "support": { - "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", - "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.297.0" - }, - "time": "2024-01-24T19:09:39+00:00" - }, - { - "name": "bower-asset/bootstrap", - "version": "v3.4.1", - "source": { - "type": "git", - "url": "https://github.com/twbs/bootstrap.git", - "reference": "68b0d231a13201eb14acd3dc84e51543d16e5f7e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twbs/bootstrap/zipball/68b0d231a13201eb14acd3dc84e51543d16e5f7e", - "reference": "68b0d231a13201eb14acd3dc84e51543d16e5f7e" - }, - "require": { - "bower-asset/jquery": ">=1.9.1,<4.0" - }, - "type": "bower-asset", - "license": [ - "MIT" - ] - }, - { - "name": "bower-asset/inputmask", - "version": "3.3.11", - "source": { - "type": "git", - "url": "git@github.com:RobinHerbots/Inputmask.git", - "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5e670ad62f50c738388d4dcec78d2888505ad77b", - "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b" - }, - "require": { - "bower-asset/jquery": ">=1.7" - }, - "type": "bower-asset", - "license": [ - "http://opensource.org/licenses/mit-license.php" - ] - }, - { - "name": "bower-asset/jquery", - "version": "3.6.4", - "source": { - "type": "git", - "url": "git@github.com:jquery/jquery-dist.git", - "reference": "91ef2d8836342875f2519b5815197ea0f23613cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/91ef2d8836342875f2519b5815197ea0f23613cf", - "reference": "91ef2d8836342875f2519b5815197ea0f23613cf" - }, - "type": "bower-asset", - "license": [ - "MIT" - ] - }, - { - "name": "bower-asset/punycode", - "version": "v1.3.2", - "source": { - "type": "git", - "url": "https://github.com/mathiasbynens/punycode.js.git", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mathiasbynens/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" - }, - "type": "bower-asset" - }, - { - "name": "bower-asset/yii2-pjax", - "version": "2.0.8", - "source": { - "type": "git", - "url": "git@github.com:yiisoft/jquery-pjax.git", - "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/a9298d57da63d14a950f1b94366a864bc62264fb", - "reference": "a9298d57da63d14a950f1b94366a864bc62264fb" - }, - "require": { - "bower-asset/jquery": ">=1.8" - }, - "type": "bower-asset", - "license": [ - "MIT" - ] - }, - { - "name": "cebe/markdown", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/cebe/markdown.git", - "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cebe/markdown/zipball/9bac5e971dd391e2802dca5400bbeacbaea9eb86", - "reference": "9bac5e971dd391e2802dca5400bbeacbaea9eb86", - "shasum": "" - }, - "require": { - "lib-pcre": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "cebe/indent": "*", - "facebook/xhprof": "*@dev", - "phpunit/phpunit": "4.1.*" - }, - "bin": [ - "bin/markdown" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "cebe\\markdown\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Creator" - } - ], - "description": "A super fast, highly extensible markdown parser for PHP", - "homepage": "https://github.com/cebe/markdown#readme", - "keywords": [ - "extensible", - "fast", - "gfm", - "markdown", - "markdown-extra" - ], - "support": { - "issues": "https://github.com/cebe/markdown/issues", - "source": "https://github.com/cebe/markdown" - }, - "time": "2018-03-26T11:24:36+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.2" - }, - "time": "2023-09-27T20:04:15+00:00" - }, - { - "name": "doctrine/lexer", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-12-14T08:49:07+00:00" - }, - { - "name": "egulias/email-validator", - "version": "3.2.6", - "source": { - "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^1.2|^2", - "php": ">=7.2", - "symfony/polyfill-intl-idn": "^1.15" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.8|^9.3.3", - "vimeo/psalm": "^4" - }, - "suggest": { - "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Egulias\\EmailValidator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eduardo Gulias Davis" - } - ], - "description": "A library for validating emails against several RFCs", - "homepage": "https://github.com/egulias/EmailValidator", - "keywords": [ - "email", - "emailvalidation", - "emailvalidator", - "validation", - "validator" - ], - "support": { - "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.6" - }, - "funding": [ - { - "url": "https://github.com/egulias", - "type": "github" - } - ], - "time": "2023-06-01T07:04:22+00:00" - }, - { - "name": "ezyang/htmlpurifier", - "version": "v4.17.0", - "source": { - "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", - "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", - "shasum": "" - }, - "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" - }, - "require-dev": { - "cerdic/css-tidy": "^1.7 || ^2.0", - "simpletest/simpletest": "dev-master" - }, - "suggest": { - "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", - "ext-bcmath": "Used for unit conversion and imagecrash protection", - "ext-iconv": "Converts text to and from non-UTF-8 encodings", - "ext-tidy": "Used for pretty-printing HTML" - }, - "type": "library", - "autoload": { - "files": [ - "library/HTMLPurifier.composer.php" - ], - "psr-0": { - "HTMLPurifier": "library/" - }, - "exclude-from-classmap": [ - "/library/HTMLPurifier/Language/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" - } - ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", - "keywords": [ - "html" - ], - "support": { - "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" - }, - "time": "2023-11-17T15:01:25+00:00" - }, - { - "name": "guzzlehttp/guzzle", - "version": "6.5.8", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/a52f0440530b54fa079ce76e8c5d196a42cad981", - "reference": "a52f0440530b54fa079ce76e8c5d196a42cad981", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.9", - "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.17" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.1" - }, - "suggest": { - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.5-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/6.5.8" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" - } - ], - "time": "2022-06-20T22:16:07+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "1.5.3", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.3" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" - } - ], - "time": "2023-05-21T12:31:43+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "1.9.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", - "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.9.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2023-04-17T16:00:37+00:00" - }, - { - "name": "mtdowling/jmespath.php", - "version": "2.7.0", - "source": { - "type": "git", - "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "symfony/polyfill-mbstring": "^1.17" - }, - "require-dev": { - "composer/xdebug-handler": "^3.0.3", - "phpunit/phpunit": "^8.5.33" - }, - "bin": [ - "bin/jp.php" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "files": [ - "src/JmesPath.php" - ], - "psr-4": { - "JmesPath\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Declaratively specify how to extract elements from a JSON document", - "keywords": [ - "json", - "jsonpath" - ], - "support": { - "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" - }, - "time": "2023-08-25T10:54:48+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v9.99.100", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", - "shasum": "" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "time": "2020-10-15T08:29:30+00:00" - }, - { - "name": "phpspec/php-diff", - "version": "v1.1.3", - "source": { - "type": "git", - "url": "https://github.com/phpspec/php-diff.git", - "reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/php-diff/zipball/fc1156187f9f6c8395886fe85ed88a0a245d72e9", - "reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9", - "shasum": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Diff": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Chris Boulton", - "homepage": "http://github.com/chrisboulton" - } - ], - "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", - "support": { - "source": "https://github.com/phpspec/php-diff/tree/v1.1.3" - }, - "time": "2020-09-18T13:47:07+00:00" - }, - { - "name": "psr/http-message", - "version": "1.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" - }, - "time": "2023-04-04T09:50:52+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "silinternational/yii2-jsonsyslog", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/silinternational/yii2-jsonsyslog.git", - "reference": "6ba9b540d950ce6baf4548596a6f158d73c31e4d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silinternational/yii2-jsonsyslog/zipball/6ba9b540d950ce6baf4548596a6f158d73c31e4d", - "reference": "6ba9b540d950ce6baf4548596a6f158d73c31e4d", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "yiisoft/yii2": "2.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Sil\\JsonSyslog\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Yii2 log target for sending data to Syslog as a JSON encoded string", - "keywords": [ - "json", - "log", - "syslog", - "yii2" - ], - "support": { - "issues": "https://github.com/silinternational/yii2-jsonsyslog/issues", - "source": "https://github.com/silinternational/yii2-jsonsyslog/tree/1.0.1" - }, - "abandoned": "silinternational/yii2-json-log-targets", - "time": "2020-12-17T20:40:29+00:00" - }, - { - "name": "swiftmailer/swiftmailer", - "version": "v6.3.0", - "source": { - "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c", - "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c", - "shasum": "" - }, - "require": { - "egulias/email-validator": "^2.0|^3.1", - "php": ">=7.0.0", - "symfony/polyfill-iconv": "^1.0", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.4" - }, - "suggest": { - "ext-intl": "Needed to support internationalized email addresses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.2-dev" - } - }, - "autoload": { - "files": [ - "lib/swift_required.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Corbyn" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", - "keywords": [ - "email", - "mail", - "mailer" - ], - "support": { - "issues": "https://github.com/swiftmailer/swiftmailer/issues", - "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", - "type": "tidelift" - } - ], - "abandoned": "symfony/mailer", - "time": "2021-10-18T15:26:12+00:00" - }, - { - "name": "symfony/polyfill-iconv", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "6de50471469b8c9afc38164452ab2b6170ee71c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/6de50471469b8c9afc38164452ab2b6170ee71c1", - "reference": "6de50471469b8c9afc38164452ab2b6170ee71c1", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-iconv": "*" - }, - "suggest": { - "ext-iconv": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Iconv extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "iconv", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", - "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:30:37+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-28T09:04:16+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", - "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" - }, - { - "name": "yiisoft/yii2", - "version": "2.0.49.3", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "783f65c9a743dfd7484b6026f1aa6f25e37159d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/783f65c9a743dfd7484b6026f1aa6f25e37159d9", - "reference": "783f65c9a743dfd7484b6026f1aa6f25e37159d9", - "shasum": "" - }, - "require": { - "bower-asset/inputmask": "~3.2.2 | ~3.3.5", - "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", - "bower-asset/punycode": "1.3.*", - "bower-asset/yii2-pjax": "~2.0.1", - "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", - "ext-ctype": "*", - "ext-mbstring": "*", - "ezyang/htmlpurifier": "^4.6", - "lib-pcre": "*", - "paragonie/random_compat": ">=1", - "php": ">=5.4.0", - "yiisoft/yii2-composer": "~2.0.4" - }, - "bin": [ - "yii" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "https://www.yiiframework.com/", - "role": "Founder and project lead" - }, - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru", - "homepage": "https://rmcreative.ru/", - "role": "Core framework development" - }, - { - "name": "Maurizio Domba", - "homepage": "http://mdomba.info/", - "role": "Core framework development" - }, - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "https://www.cebe.cc/", - "role": "Core framework development" - }, - { - "name": "Timur Ruziev", - "email": "resurtm@gmail.com", - "homepage": "http://resurtm.com/", - "role": "Core framework development" - }, - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com", - "role": "Core framework development" - }, - { - "name": "Dmitry Naumenko", - "email": "d.naumenko.a@gmail.com", - "role": "Core framework development" - }, - { - "name": "Boudewijn Vahrmeijer", - "email": "info@dynasource.eu", - "homepage": "http://dynasource.eu", - "role": "Core framework development" - } - ], - "description": "Yii PHP Framework Version 2", - "homepage": "https://www.yiiframework.com/", - "keywords": [ - "framework", - "yii2" - ], - "support": { - "forum": "https://forum.yiiframework.com/", - "irc": "ircs://irc.libera.chat:6697/yii", - "issues": "https://github.com/yiisoft/yii2/issues?state=open", - "source": "https://github.com/yiisoft/yii2", - "wiki": "https://www.yiiframework.com/wiki" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2", - "type": "tidelift" - } - ], - "time": "2023-10-31T15:39:08+00:00" - }, - { - "name": "yiisoft/yii2-bootstrap", - "version": "2.0.11", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-bootstrap.git", - "reference": "83d144f4089adaa7064ad60dc4c1436daa2eb30e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/83d144f4089adaa7064ad60dc4c1436daa2eb30e", - "reference": "83d144f4089adaa7064ad60dc4c1436daa2eb30e", - "shasum": "" - }, - "require": { - "bower-asset/bootstrap": "3.4.* | 3.3.* | 3.2.* | 3.1.*", - "yiisoft/yii2": "~2.0.6" - }, - "require-dev": { - "cweagans/composer-patches": "^1.7", - "phpunit/phpunit": "4.8.34" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - }, - "patches": { - "phpunit/phpunit-mock-objects": { - "Fix PHP 7 and 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_mock_objects.patch" - }, - "phpunit/phpunit": { - "Fix PHP 7 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php7.patch", - "Fix PHP 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php8.patch" - } - } - }, - "autoload": { - "psr-4": { - "yii\\bootstrap\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/" - }, - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru", - "homepage": "http://rmcreative.ru/" - }, - { - "name": "Antonio Ramirez", - "email": "amigo.cobos@gmail.com" - }, - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com" - } - ], - "description": "The Twitter Bootstrap extension for the Yii framework", - "keywords": [ - "bootstrap", - "yii2" - ], - "support": { - "forum": "http://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/yii2-bootstrap/issues", - "source": "https://github.com/yiisoft/yii2-bootstrap", - "wiki": "http://www.yiiframework.com/wiki/" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-bootstrap", - "type": "tidelift" - } - ], - "time": "2021-08-09T20:54:06+00:00" - }, - { - "name": "yiisoft/yii2-composer", - "version": "2.0.10", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/94bb3f66e779e2774f8776d6e1bdeab402940510", - "reference": "94bb3f66e779e2774f8776d6e1bdeab402940510", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 | ^2.0" - }, - "require-dev": { - "composer/composer": "^1.0 | ^2.0@dev", - "phpunit/phpunit": "<7" - }, - "type": "composer-plugin", - "extra": { - "class": "yii\\composer\\Plugin", - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\composer\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - }, - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc" - } - ], - "description": "The composer plugin for Yii extension installer", - "keywords": [ - "composer", - "extension installer", - "yii2" - ], - "support": { - "forum": "http://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/yii2-composer/issues", - "source": "https://github.com/yiisoft/yii2-composer", - "wiki": "http://www.yiiframework.com/wiki/" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-composer", - "type": "tidelift" - } - ], - "time": "2020-06-24T00:04:01+00:00" - }, - { - "name": "yiisoft/yii2-gii", - "version": "2.2.6", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-gii.git", - "reference": "ac574e7e2c29fd865145c8688719f252d19aae23" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-gii/zipball/ac574e7e2c29fd865145c8688719f252d19aae23", - "reference": "ac574e7e2c29fd865145c8688719f252d19aae23", - "shasum": "" - }, - "require": { - "phpspec/php-diff": "^1.1.0", - "yiisoft/yii2": "~2.0.46" - }, - "require-dev": { - "cweagans/composer-patches": "^1.7", - "phpunit/phpunit": "4.8.34", - "yiisoft/yii2-coding-standards": "~2.0" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - }, - "composer-exit-on-patch-failure": true, - "patches": { - "phpunit/phpunit-mock-objects": { - "Fix PHP 7 and 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_mock_objects.patch" - }, - "phpunit/php-file-iterator": { - "Fix PHP 8.1 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_path_file_iterator.patch" - }, - "phpunit/phpunit": { - "Fix PHP 7 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php7.patch", - "Fix PHP 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php8.patch", - "Fix PHP 8.1 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php81.patch" - } - } - }, - "autoload": { - "psr-4": { - "yii\\gii\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - } - ], - "description": "The Gii extension for the Yii framework", - "keywords": [ - "code generator", - "gii", - "yii2" - ], - "support": { - "forum": "https://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/yii2-gii/issues", - "source": "https://github.com/yiisoft/yii2-gii", - "wiki": "https://www.yiiframework.com/wiki/" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-gii", - "type": "tidelift" - } - ], - "time": "2023-05-22T20:55:37+00:00" - }, - { - "name": "yiisoft/yii2-swiftmailer", - "version": "2.1.3", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-swiftmailer.git", - "reference": "7b7ec871b4a63c0abbcd10e1ee3fb5be22f8b340" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-swiftmailer/zipball/7b7ec871b4a63c0abbcd10e1ee3fb5be22f8b340", - "reference": "7b7ec871b4a63c0abbcd10e1ee3fb5be22f8b340", - "shasum": "" - }, - "require": { - "swiftmailer/swiftmailer": "~6.0", - "yiisoft/yii2": ">=2.0.4" - }, - "require-dev": { - "cweagans/composer-patches": "^1.7", - "phpunit/phpunit": "4.8.34" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev" - }, - "composer-exit-on-patch-failure": true, - "patches": { - "phpunit/phpunit-mock-objects": { - "Fix PHP 7 and 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_mock_objects.patch" - }, - "phpunit/phpunit": { - "Fix PHP 7 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php7.patch", - "Fix PHP 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php8.patch" - } - } - }, - "autoload": { - "psr-4": { - "yii\\swiftmailer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com" - } - ], - "description": "The SwiftMailer integration for the Yii framework", - "keywords": [ - "email", - "mail", - "mailer", - "swift", - "swiftmailer", - "yii2" - ], - "support": { - "forum": "http://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/yii2-swiftmailer/issues", - "source": "https://github.com/yiisoft/yii2-swiftmailer", - "wiki": "http://www.yiiframework.com/wiki/" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-swiftmailer", - "type": "tidelift" - } - ], - "time": "2021-12-30T08:48:48+00:00" - } - ], - "packages-dev": [ - { - "name": "behat/gherkin", - "version": "v4.9.0", - "source": { - "type": "git", - "url": "https://github.com/Behat/Gherkin.git", - "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", - "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", - "shasum": "" - }, - "require": { - "php": "~7.2|~8.0" - }, - "require-dev": { - "cucumber/cucumber": "dev-gherkin-22.0.0", - "phpunit/phpunit": "~8|~9", - "symfony/yaml": "~3|~4|~5" - }, - "suggest": { - "symfony/yaml": "If you want to parse features, represented in YAML files" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "autoload": { - "psr-0": { - "Behat\\Gherkin": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Gherkin DSL parser for PHP", - "homepage": "http://behat.org/", - "keywords": [ - "BDD", - "Behat", - "Cucumber", - "DSL", - "gherkin", - "parser" - ], - "support": { - "issues": "https://github.com/Behat/Gherkin/issues", - "source": "https://github.com/Behat/Gherkin/tree/v4.9.0" - }, - "time": "2021-10-12T13:05:09+00:00" - }, - { - "name": "codeception/codeception", - "version": "2.5.6", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Codeception.git", - "reference": "b83a9338296e706fab2ceb49de8a352fbca3dc98" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b83a9338296e706fab2ceb49de8a352fbca3dc98", - "reference": "b83a9338296e706fab2ceb49de8a352fbca3dc98", - "shasum": "" - }, - "require": { - "behat/gherkin": "^4.4.0", - "codeception/phpunit-wrapper": "^6.0.9|^7.0.6", - "codeception/stub": "^2.0", - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "facebook/webdriver": ">=1.1.3 <2.0", - "guzzlehttp/guzzle": ">=4.1.4 <7.0", - "guzzlehttp/psr7": "~1.0", - "php": ">=5.6.0 <8.0", - "symfony/browser-kit": ">=2.7 <5.0", - "symfony/console": ">=2.7 <5.0", - "symfony/css-selector": ">=2.7 <5.0", - "symfony/dom-crawler": ">=2.7 <5.0", - "symfony/event-dispatcher": ">=2.7 <5.0", - "symfony/finder": ">=2.7 <5.0", - "symfony/yaml": ">=2.7 <5.0" - }, - "require-dev": { - "codeception/specify": "~0.3", - "facebook/graph-sdk": "~5.3", - "flow/jsonpath": "~0.2", - "monolog/monolog": "~1.8", - "pda/pheanstalk": "~3.0", - "php-amqplib/php-amqplib": "~2.4", - "predis/predis": "^1.0", - "squizlabs/php_codesniffer": "~2.0", - "symfony/process": ">=2.7 <5.0", - "vlucas/phpdotenv": "^3.0" - }, - "suggest": { - "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", - "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", - "codeception/specify": "BDD-style code blocks", - "codeception/verify": "BDD-style assertions", - "flow/jsonpath": "For using JSONPath in REST module", - "league/factory-muffin": "For DataFactory module", - "league/factory-muffin-faker": "For Faker support in DataFactory module", - "phpseclib/phpseclib": "for SFTP option in FTP Module", - "stecman/symfony-console-completion": "For BASH autocompletion", - "symfony/phpunit-bridge": "For phpunit-bridge support" - }, - "bin": [ - "codecept" - ], - "type": "library", - "extra": { - "branch-alias": [] - }, - "autoload": { - "psr-4": { - "Codeception\\": "src/Codeception", - "Codeception\\Extension\\": "ext" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert@mail.ua", - "homepage": "http://codegyre.com" - } - ], - "description": "BDD-style testing framework", - "homepage": "http://codeception.com/", - "keywords": [ - "BDD", - "TDD", - "acceptance testing", - "functional testing", - "unit testing" - ], - "support": { - "issues": "https://github.com/Codeception/Codeception/issues", - "source": "https://github.com/Codeception/Codeception/tree/2.5.6" - }, - "time": "2019-04-24T11:28:19+00:00" - }, - { - "name": "codeception/phpunit-wrapper", - "version": "7.8.4", - "source": { - "type": "git", - "url": "https://github.com/Codeception/phpunit-wrapper.git", - "reference": "dd44fc152433d27d3de03d59b4945449b3407af0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/dd44fc152433d27d3de03d59b4945449b3407af0", - "reference": "dd44fc152433d27d3de03d59b4945449b3407af0", - "shasum": "" - }, - "require": { - "phpunit/php-code-coverage": "^6.0", - "phpunit/phpunit": "7.5.*", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0" - }, - "require-dev": { - "codeception/specify": "*", - "vlucas/phpdotenv": "^3.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Codeception\\PHPUnit\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Davert", - "email": "davert.php@resend.cc" - } - ], - "description": "PHPUnit classes used by Codeception", - "support": { - "issues": "https://github.com/Codeception/phpunit-wrapper/issues", - "source": "https://github.com/Codeception/phpunit-wrapper/tree/7.8.4" - }, - "time": "2022-05-23T06:09:22+00:00" - }, - { - "name": "codeception/specify", - "version": "0.4.6", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Specify.git", - "reference": "21b586f503ca444aa519dd9cafb32f113a05f286" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Specify/zipball/21b586f503ca444aa519dd9cafb32f113a05f286", - "reference": "21b586f503ca444aa519dd9cafb32f113a05f286", - "shasum": "" - }, - "require": { - "myclabs/deep-copy": "~1.1", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Codeception\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert.php@mailican.com" - } - ], - "description": "BDD code blocks for PHPUnit and Codeception", - "support": { - "issues": "https://github.com/Codeception/Specify/issues", - "source": "https://github.com/Codeception/Specify/tree/master" - }, - "time": "2016-10-21T09:42:00+00:00" - }, - { - "name": "codeception/stub", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Stub.git", - "reference": "853657f988942f7afb69becf3fd0059f192c705a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/853657f988942f7afb69becf3fd0059f192c705a", - "reference": "853657f988942f7afb69becf3fd0059f192c705a", - "shasum": "" - }, - "require": { - "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Codeception\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "support": { - "issues": "https://github.com/Codeception/Stub/issues", - "source": "https://github.com/Codeception/Stub/tree/master" - }, - "time": "2019-03-02T15:35:10+00:00" - }, - { - "name": "codeception/verify", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/Codeception/Verify.git", - "reference": "8a17273017e23a866df3fa2ad2b4182b7ce354f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Codeception/Verify/zipball/8a17273017e23a866df3fa2ad2b4182b7ce354f0", - "reference": "8a17273017e23a866df3fa2ad2b4182b7ce354f0", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/Codeception/function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert.php@mailican.com" - } - ], - "description": "BDD assertion library for PHPUnit", - "support": { - "issues": "https://github.com/Codeception/Verify/issues", - "source": "https://github.com/Codeception/Verify/tree/0.4.0" - }, - "time": "2017-07-12T16:50:18+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:15:36+00:00" - }, - { - "name": "facebook/webdriver", - "version": "1.7.1", - "source": { - "type": "git", - "url": "https://github.com/php-webdriver/php-webdriver-archive.git", - "reference": "e43de70f3c7166169d0f14a374505392734160e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver-archive/zipball/e43de70f3c7166169d0f14a374505392734160e5", - "reference": "e43de70f3c7166169d0f14a374505392734160e5", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-zip": "*", - "php": "^5.6 || ~7.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "php-coveralls/php-coveralls": "^2.0", - "php-mock/php-mock-phpunit": "^1.1", - "phpunit/phpunit": "^5.7", - "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", - "squizlabs/php_codesniffer": "^2.6", - "symfony/var-dumper": "^3.3 || ^4.0" - }, - "suggest": { - "ext-SimpleXML": "For Firefox profile creation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-community": "1.5-dev" - } - }, - "autoload": { - "psr-4": { - "Facebook\\WebDriver\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "A PHP client for Selenium WebDriver", - "homepage": "https://github.com/facebook/php-webdriver", - "keywords": [ - "facebook", - "php", - "selenium", - "webdriver" - ], - "support": { - "forum": "https://www.facebook.com/groups/phpwebdriver/", - "issues": "https://github.com/facebook/php-webdriver/issues", - "source": "https://github.com/facebook/php-webdriver" - }, - "abandoned": "php-webdriver/webdriver", - "time": "2019-06-13T08:02:18+00:00" - }, - { - "name": "fakerphp/faker", - "version": "v1.23.1", - "source": { - "type": "git", - "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "conflict": { - "fzaninotto/faker": "*" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "doctrine/persistence": "^1.3 || ^2.0", - "ext-intl": "*", - "phpunit/phpunit": "^9.5.26", - "symfony/phpunit-bridge": "^5.4.16" - }, - "suggest": { - "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", - "ext-curl": "Required by Faker\\Provider\\Image to download images.", - "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", - "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", - "ext-mbstring": "Required for multibyte Unicode string functionality." - }, - "type": "library", - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "support": { - "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" - }, - "time": "2024-01-02T13:46:09+00:00" - }, - { - "name": "fillup/phpmyadmin-minimal", - "version": "4.2.2", - "source": { - "type": "git", - "url": "https://github.com/fillup/phpmyadmin-minimal.git", - "reference": "395a8dbfb9ea60e18e2a2c5226bef921cb3a87ac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fillup/phpmyadmin-minimal/zipball/395a8dbfb9ea60e18e2a2c5226bef921cb3a87ac", - "reference": "395a8dbfb9ea60e18e2a2c5226bef921cb3a87ac", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "application", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0+" - ], - "authors": [ - { - "name": "The phpMyAdmin Team", - "email": "phpmyadmin-devel@lists.sourceforge.net", - "homepage": "http://www.phpmyadmin.net/home_page/team.php" - } - ], - "description": "Minmal version of MySQL web administration tool", - "homepage": "http://www.phpmyadmin.net/", - "keywords": [ - "mysql", - "phpmyadmin", - "web" - ], - "support": { - "forum": "https://sourceforge.net/p/phpmyadmin/discussion/Help", - "issues": "https://sourceforge.net/p/phpmyadmin/bugs/", - "source": "https://github.com/phpmyadmin/phpmyadmin", - "wiki": "http://wiki.phpmyadmin.net/" - }, - "time": "2015-06-16T12:42:01+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.11.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2023-03-08T13:26:56+00:00" - }, - { - "name": "phar-io/manifest", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" - }, - "time": "2018-07-08T19:23:20+00:00" - }, - { - "name": "phar-io/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/master" - }, - "time": "2018-07-08T19:19:57+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.8.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fad452781b3d774e3337b0c0b245dd8e5a4455fc", - "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" - }, - "require-dev": { - "ext-tokenizer": "*", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.0" - }, - "time": "2024-01-11T11:49:22+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.18.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d4f454f7e1193933f04e6500de3e79191648ed0c", - "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0 || ^5.0", - "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "dev", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.18.0" - }, - "time": "2023-12-07T16:22:33+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "1.25.0", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", - "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" - }, - "time": "2024-01-04T17:06:16+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "6.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "suggest": { - "ext-xdebug": "^2.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master" - }, - "time": "2018-10-31T16:06:48+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "2.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:42:26+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" - }, - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "2.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T08:20:02+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "3.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "abandoned": true, - "time": "2021-07-26T12:15:06+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "7.5.20", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", - "sebastian/version": "^2.0.1" - }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.5-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20" - }, - "time": "2020-01-08T08:45:45+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T08:15:22+00:00" - }, - { - "name": "sebastian/comparator", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T12:31:48+00:00" - }, - { - "name": "sebastian/diff", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6296a0c086dd0117c1b78b059374d7fcbe7545ae", - "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-05-07T05:30:20+00:00" - }, - { - "name": "sebastian/environment", - "version": "4.2.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:53:42+00:00" - }, - { - "name": "sebastian/exporter", - "version": "3.1.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/73a9676f2833b9a7c36968f9d882589cd75511e6", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^8.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T06:00:17+00:00" - }, - { - "name": "sebastian/global-state", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0" - }, - "time": "2017-04-27T15:39:26+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "shasum": "" - }, - "require": { - "php": ">=7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:40:27+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:37:18+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:34:24+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-30T07:30:19+00:00" - }, - { - "name": "sebastian/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" - }, - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "symfony/browser-kit", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "2a1ff40723ef6b29c8229a860a9c8f815ad7dbbb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/2a1ff40723ef6b29c8229a860a9c8f815ad7dbbb", - "reference": "2a1ff40723ef6b29c8229a860a9c8f815ad7dbbb", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/dom-crawler": "^3.4|^4.0|^5.0", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/http-client": "^4.3|^5.0", - "symfony/mime": "^4.3|^5.0", - "symfony/process": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/browser-kit/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-25T12:56:14+00:00" - }, - { - "name": "symfony/console", - "version": "v4.4.49", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", - "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3|>=5", - "symfony/lock": "<4.4", - "symfony/process": "<3.3" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.3", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^4.3|^5.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/console/tree/v4.4.49" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-05T17:10:16+00:00" - }, - { - "name": "symfony/css-selector", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "bd0a6737e48de45b4b0b7b6fc98c78404ddceaed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/bd0a6737e48de45b4b0b7b6fc98c78404ddceaed", - "reference": "bd0a6737e48de45b4b0b7b6fc98c78404ddceaed", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Converts CSS selectors to XPath expressions", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/css-selector/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T13:16:42+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, - { - "name": "symfony/dom-crawler", - "version": "v4.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4b8daf6c56801e6d664224261cb100b73edc78a5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4b8daf6c56801e6d664224261cb100b73edc78a5", - "reference": "4b8daf6c56801e6d664224261cb100b73edc78a5", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "masterminds/html5": "<2.6" - }, - "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases DOM navigation for HTML and XML documents", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v4.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-03T12:57:57+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1e866e9e5c1b22168e0ce5f0b467f19bba61266a", - "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/event-dispatcher-contracts": "^1.1", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/dependency-injection": "<3.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/error-handler": "~3.4|~4.4", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T09:59:04+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v1.10.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/761c8b8387cfe5f8026594a75fdf0a4e83ba6974", - "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974", - "shasum": "" - }, - "require": { - "php": ">=7.1.3" - }, - "suggest": { - "psr/event-dispatcher": "", - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.1-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.10.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T09:59:04+00:00" - }, - { - "name": "symfony/finder", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "66bd787edb5e42ff59d3523f623895af05043e4f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/66bd787edb5e42ff59d3523f623895af05043e4f", - "reference": "66bd787edb5e42ff59d3523f623895af05043e4f", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-29T07:35:46+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" - }, - { - "name": "symfony/process", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", - "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T13:16:42+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v1.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0", - "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "suggest": { - "psr/container": "", - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v1.1.2" - }, - "time": "2019-05-28T07:50:59+00:00" - }, - { - "name": "symfony/yaml", - "version": "v4.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", - "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-02T15:47:23+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2023-11-20T00:12:19+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" - }, - { - "name": "yiisoft/yii2-codeception", - "version": "2.0.6", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-codeception.git", - "reference": "086c8c2d28736e7a484a7a8611b5cc84024e9fb3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-codeception/zipball/086c8c2d28736e7a484a7a8611b5cc84024e9fb3", - "reference": "086c8c2d28736e7a484a7a8611b5cc84024e9fb3", - "shasum": "" - }, - "require": { - "yiisoft/yii2": ">=2.0.4" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\codeception\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Mark Jebri", - "email": "mark.github@yandex.ru" - } - ], - "description": "The Codeception integration for the Yii framework", - "keywords": [ - "codeception", - "yii2" - ], - "support": { - "forum": "http://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/yii2-codeception/issues", - "source": "https://github.com/yiisoft/yii2-codeception", - "wiki": "http://www.yiiframework.com/wiki/" - }, - "abandoned": "codeception/codeception", - "time": "2017-05-22T12:08:21+00:00" - }, - { - "name": "yiisoft/yii2-debug", - "version": "2.1.25", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-debug.git", - "reference": "4d011b9bfc83bde71cde43c9f6837f5a74685ea7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/4d011b9bfc83bde71cde43c9f6837f5a74685ea7", - "reference": "4d011b9bfc83bde71cde43c9f6837f5a74685ea7", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.4", - "yiisoft/yii2": "~2.0.13" - }, - "require-dev": { - "cweagans/composer-patches": "^1.7", - "phpunit/phpunit": "4.8.34", - "yiisoft/yii2-coding-standards": "~2.0", - "yiisoft/yii2-swiftmailer": "*" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - }, - "composer-exit-on-patch-failure": true, - "patches": { - "phpunit/phpunit-mock-objects": { - "Fix PHP 7 and 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_mock_objects.patch" - }, - "phpunit/phpunit": { - "Fix PHP 7 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php7.patch", - "Fix PHP 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php8.patch", - "Fix PHP 8.1 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php81.patch" - } - } - }, - "autoload": { - "psr-4": { - "yii\\debug\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - }, - { - "name": "Simon Karlen", - "email": "simi.albi@outlook.com" - } - ], - "description": "The debugger extension for the Yii framework", - "keywords": [ - "debug", - "debugger", - "yii2" - ], - "support": { - "forum": "http://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/yii2-debug/issues", - "source": "https://github.com/yiisoft/yii2-debug", - "wiki": "http://www.yiiframework.com/wiki/" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-debug", - "type": "tidelift" - } - ], - "time": "2023-09-26T15:50:00+00:00" - }, - { - "name": "yiisoft/yii2-faker", - "version": "2.0.5", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-faker.git", - "reference": "8c361657143bfaea58ff7dcc9bf51f1991a46f5d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-faker/zipball/8c361657143bfaea58ff7dcc9bf51f1991a46f5d", - "reference": "8c361657143bfaea58ff7dcc9bf51f1991a46f5d", - "shasum": "" - }, - "require": { - "fakerphp/faker": "~1.9|~1.10", - "yiisoft/yii2": "~2.0.0" - }, - "require-dev": { - "cweagans/composer-patches": "^1.7", - "phpunit/phpunit": "4.8.34" - }, - "type": "yii2-extension", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - }, - "composer-exit-on-patch-failure": true, - "patches": { - "phpunit/phpunit-mock-objects": { - "Fix PHP 7 and 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_mock_objects.patch" - }, - "phpunit/phpunit": { - "Fix PHP 7 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php7.patch", - "Fix PHP 8 compatibility": "https://yiisoft.github.io/phpunit-patches/phpunit_php8.patch" - } - } - }, - "autoload": { - "psr-4": { - "yii\\faker\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Mark Jebri", - "email": "mark.github@yandex.ru" - } - ], - "description": "Fixture generator. The Faker integration for the Yii framework.", - "keywords": [ - "Fixture", - "faker", - "yii2" - ], - "support": { - "forum": "http://www.yiiframework.com/forum/", - "irc": "irc://irc.freenode.net/yii", - "issues": "https://github.com/yiisoft/yii2-faker/issues", - "source": "https://github.com/yiisoft/yii2-faker", - "wiki": "http://www.yiiframework.com/wiki/" - }, - "funding": [ - { - "url": "https://github.com/yiisoft", - "type": "github" - }, - { - "url": "https://opencollective.com/yiisoft", - "type": "open_collective" - }, - { - "url": "https://tidelift.com/funding/github/packagist/yiisoft/yii2-faker", - "type": "tidelift" - } - ], - "time": "2020-11-10T12:27:35+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=7.4.0" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/application/console/components/ActionCommon.php b/application/console/components/ActionCommon.php deleted file mode 100644 index c2d12ef5..00000000 --- a/application/console/components/ActionCommon.php +++ /dev/null @@ -1,36 +0,0 @@ - 20) { - $tmp = 0; - } - $exit = ($tmp > 0); - $tmp = $tmp + 1; - $tmp = shm_put_var($tokenValue, 6, $tmp); - $tmp = shm_get_var($tokenValue, 6); - sem_release($tokenSemaphore); - if ($exit) return false; - return true; - } - function release($tokenSemaphore, $tokenValue) { - sem_acquire($tokenSemaphore); - $tmp = shm_get_var($tokenValue, 6); - $tmp = shm_put_var($tokenValue, 6, 0); - $tmp = shm_get_var($tokenValue, 6); - sem_release($tokenSemaphore); - } -} - - diff --git a/application/console/components/AwsStartupAction.php b/application/console/components/AwsStartupAction.php deleted file mode 100644 index e27c9a0d..00000000 --- a/application/console/components/AwsStartupAction.php +++ /dev/null @@ -1,135 +0,0 @@ - $cacheLocation, - 'type' => 'S3', - ]; - $source = [ - 'buildspec' => 'version: 0.2', - 'gitCloneDepth' => 1, - 'location' => 'https://git-codecommit.us-east-1.amazonaws.com/v1/repos/sample', - 'type' => 'CODECOMMIT', - ]; - $this->createProject($projectName, $cache, $source, $logger); - - // Build publish role if necessary - // Copy default file - $projectName = 'publish_app'; - $prefix = 'arn:aws:s3:::'; - $bucket = S3::getArtifactsBucket(); - $baseUrl = 'default/default.zip'; - $defaultLocation = $prefix . $bucket . "/" . $baseUrl; - echo "Location: $defaultLocation" . PHP_EOL; - echo "[$prefix] AwsStartupAction: create CodeBuild project: $projectName" . PHP_EOL; - $this->copyDefaultProjectFolder($logger); - $cache = [ - 'type' => 'NO_CACHE', - ]; - $source = [ - 'buildspec' => 'version: 0.2', - 'gitCloneDepth' => 1, - 'location' => $defaultLocation, - 'type' => 'S3', - ]; - $this->createProject($projectName, $cache, $source, $logger); - - $this ->copyBuildScriptFolder($logger); - - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] createCodeBuildProject: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = [ - 'problem' => 'Failed to create CodeBuild Project', - 'projectName' => $projectName - ]; - $logger->appbuilderExceptionLog($logException, $e); - } - } - private function createProject($projectName, $cache, $source, $logger) - { - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] AwsStartupAction: create CodeBuild project: $projectName" . PHP_EOL; - $codeBuild = new CodeBuild(); - $iamWrapper = new IAmWrapper(); - if (!$codeBuild->projectExists($projectName)) - { - echo " Creating build project " . $projectName . PHP_EOL; - - $roleArn = $iamWrapper->getRoleArn($projectName); - $codeBuild->createProject($projectName, $roleArn, $cache, $source); - echo " Project created" . PHP_EOL; - } - - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] createCodeBuildProject: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = [ - 'problem' => 'Failed to create CodeBuild Project', - 'projectName' => $projectName - ]; - $logger->appbuilderExceptionLog($logException, $e); - } - } - private function copyDefaultProjectFolder($logger) - { - $prefix = Utils::getPrefix(); - echo "[$prefix] AwsStartupAction: copyDefaultProjectFolder" . PHP_EOL; - $sourceFolder = '/data/console/views/cron/scripts/project_default'; - $bucket = S3::getArtifactsBucket(); - $this->copyFolder($logger, $sourceFolder, $bucket); - } - private function copyBuildScriptFolder($logger) - { - $prefix = Utils::getPrefix(); - echo "[$prefix] AwsStartupAction: copyBuildScriptFolder" . PHP_EOL; - $sourceFolder = '/data/console/views/cron/scripts/upload'; - $bucket = S3::getProjectsBucket(); - $this->copyFolder($logger, $sourceFolder, $bucket); - } - private function copyFolder($logger, $sourceFolder, $bucket) - { - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] AwsStartupAction: copyFolder" . PHP_EOL; - $s3 = new S3(); - $s3->uploadFolder($sourceFolder, $bucket); - echo " Copy completed" . PHP_EOL; - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] copyFolder: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = [ - 'problem' => 'Failed to copy folder' - ]; - $logger->appbuilderExceptionLog($logException, $e); - } - } -} diff --git a/application/console/components/CopyErrorToS3Operation.php b/application/console/components/CopyErrorToS3Operation.php deleted file mode 100644 index 397c17a7..00000000 --- a/application/console/components/CopyErrorToS3Operation.php +++ /dev/null @@ -1,58 +0,0 @@ -id = $id; - $this->parms = $parms; - } - public function performOperation() - { - $prefix = Utils::getPrefix(); - echo "[$prefix] CopyErrorToS3Operation ID: " .$this->id . PHP_EOL; - if ($this->parms == "release") { - $release = Release::findOne(['id' => $this->id]); - if ($release) { - $s3 = new S3(); - $s3->copyS3Folder($release); - $release->status = Release::STATUS_COMPLETED; - $release->save(); - } - } else { - $build = Build::findOneByBuildId($this->id); - if ($build) { - $s3 = new S3(); - $s3->copyS3Folder($build); - $build->status = Build::STATUS_COMPLETED; - $build->save(); - } - } - } - public function getMaximumRetries() - { - return $this->maxRetries; - } - public function getMaximumDelay() - { - return $this->maxDelay; - } - public function getAlertAfterAttemptCount() - { - return $this->alertAfter; - } -} diff --git a/application/console/components/CopyToS3Operation.php b/application/console/components/CopyToS3Operation.php deleted file mode 100644 index e852cb3d..00000000 --- a/application/console/components/CopyToS3Operation.php +++ /dev/null @@ -1,234 +0,0 @@ -build_id = $id; - $this->parms = $parms; - $this->fileUtil = \Yii::$container->get('fileUtils'); - } - public function performOperation() - { - $prefix = Utils::getPrefix(); - echo "[$prefix] CopyToS3Operation ID: " .$this->build_id . PHP_EOL; - if ($this->parms == "release") { - $release = Release::findOneByBuildId($this->build_id); - if ($release) { - $s3 = new S3(); - $s3->copyS3Folder($release); - $release->status = Release::STATUS_COMPLETED; - $release->save(); - $s3->removeCodeBuildFolder($release); - } - } - else - { - $build = Build::findOneByBuildId($this->build_id); - if ($build) { - $job = $build->job; - if ($job){ - $this->saveBuild($build); - $build->status = Build::STATUS_COMPLETED; - $build->result = Build::RESULT_SUCCESS; - if (!$build->save()){ - throw new \Exception("Unable to update Build entry, model errors: ".print_r($build->getFirstErrors(),true), 1450216434); - } - $this->s3->removeCodeBuildFolder($build); - } - } - } - } - public function getMaximumRetries() - { - return $this->maxRetries; - } - public function getMaximumDelay() - { - return $this->maxDelay; - } - public function getAlertAfterAttemptCount() - { - return $this->alertAfter; - } - - /** - * @param Build $build - * @param string $defaultLanguage - */ - private function getExtraContent($build, $defaultLanguage) { - echo 'getExtraContent defaultLanguage: ' . $defaultLanguage . PHP_EOL; - $manifestFileContent = (string)$this->s3->readS3File($build, 'manifest.txt'); - if (!empty($manifestFileContent)) { - $manifestFiles = explode("\n", $manifestFileContent); - if (count($manifestFiles) > 0) { - // Copy index.html to destination folder - $file = \Yii::getAlias("@common") . "/preview/playlisting/index.html"; - - $indexContents = $this->fileUtil->file_get_contents($file); - $this->s3->writeFileToS3($indexContents, "play-listing/index.html", $build); - } - if (empty($defaultLanguage)) { - // If defaultLanguage was not found, use first entry with icon - foreach ($manifestFiles as $playListingFile) { - if (preg_match("/([^\/]*)\/images\/icon.png$/", $playListingFile, $matches)) { - $defaultLanguage = $matches[1]; - break; - } - } - } - - // Note: I tried using array_map/array_filter, but it changed the json - // serialization from an array to a hash where the indexes were the old - // positions in the array. - $playEncodedRelativePaths = array(); - $languages = array(); - $publishIndex = "
      " . PHP_EOL; - $ignoreFiles = [ "default-language.txt", "primary-color.txt", "download-apk-strings.json" ]; - foreach ($manifestFiles as $path) { - if (in_array($path, $ignoreFiles)) { - continue; - } - if (!empty($path)) { - // collect files - $encodedPath = self::encodePath('play-listing/' . $path); - $publishIndex .= "
    • play-listing/$path

    • " . PHP_EOL; - array_push($playEncodedRelativePaths, self::encodePath($path)); - - // collect languages - if (preg_match("/([^\/]*)\//", $path, $langMatches)) { - $lang = $langMatches[1]; - if (!in_array($lang, $languages)) { - array_push($languages, $lang); - } - } - } - } - $publishIndex .= "
    " . PHP_EOL; - $this->s3->writeFileToS3($publishIndex, 'play-listing.html', $build); - $manifest = [ "files" => $playEncodedRelativePaths, - "languages" => $languages, - "color" => $this->getPrimaryColor($build), - "package" => $this->getPackageName($build), - "download-apk-strings" => $this->getDownloadApkStrings($build, $languages, $defaultLanguage), - "url" => $build->getArtifactUrlBase() . "play-listing/" ]; - if (!empty($defaultLanguage)) { - $manifest["default-language"] = $defaultLanguage; - $manifest["icon"] = "$defaultLanguage/images/icon.png"; - } - $json = json_encode($manifest, JSON_UNESCAPED_SLASHES); - $jsonFileName = 'play-listing/manifest.json'; - $this->s3->writeFileToS3($json, $jsonFileName, $build); - } - } - - private static function encodePath($path) { - $encode = function($value) { - return urlencode($value); - }; - - return implode("/", array_map($encode,explode("/", $path))); - } - - - /** - * getDefaultLanguage reads the default language from default-language.txt - * - * @param Build $build - Current build object - * @return string Contents of default-language.txt or empty if file doesn't exist - */ - private function getDefaultLanguage($build) { - $defaultLanguage = $this->s3->readS3File($build, 'play-listing/default-language.txt'); - return $defaultLanguage; - } - - /** - * getPrimaryColor read the primary color from primary-color.txt - * - * @param Build $build - Current build object - * @return string Contents of primary-color.txt or default value is file doesn't exist - */ - private function getPrimaryColor($build) { - $primaryColor = trim($this->s3->readS3File($build, 'play-listing/primary-color.txt')); - if (strlen($primaryColor) == 0) { - $primaryColor = "#cce2ff"; - } - return $primaryColor; - } - - /** - * getDownloadApkStrings read the localization strings in download-apk-strings.json - * - * @param Build $build - Current build object - * @param array $languages - languages to include - * @param string $defaultLanguage - the default language - * @return mixed Contents of download-apk-strings.json or default as array - */ - private function getDownloadApkStrings($build, $languages, $defaultLanguage) { - - $strings = trim($this->s3->readS3File($build, 'play-listing/download-apk-strings.json')); - if (strlen($strings) === 0) { - $strings = '{ "en" : "Download APK" }'; - } - $downloadApkStrings = array(); - $languageNoVariant = function($lang) { - return substr($lang, 0, 2); - }; - $languagesNoVariant = array_map($languageNoVariant, $languages); - foreach(json_decode($strings) as $lang => $downloadApkString) - { - if (in_array($lang, $languagesNoVariant)) { - $downloadApkStrings[$lang] = $downloadApkString; - } - } - if (count($downloadApkStrings) === 0) { - return [$defaultLanguage => "APK"]; - } - return $downloadApkStrings; - } - - private function getPackageName($build) { - $packageName = trim($this->s3->readS3File($build, 'package_name.txt')); - return $packageName; - } - - /** - * Save the build to S3. - * @param Build $build - * @return string - */ - private function saveBuild($build) { - $logger = new Appbuilder_logger("CopyToS3Operation"); - $this->s3 = new S3(); - - $this->s3->copyS3Folder($build); - $defaultLanguage = $this->getDefaultLanguage($build); - $this->getExtraContent($build, $defaultLanguage); - - # Log - $log = Build::getlogBuildDetails($build); - - $logger->appbuilderWarningLog($log); - - } -} diff --git a/application/console/components/DevelopmentAction.php b/application/console/components/DevelopmentAction.php deleted file mode 100644 index 9d8a0137..00000000 --- a/application/console/components/DevelopmentAction.php +++ /dev/null @@ -1,483 +0,0 @@ -actionType = $argv[0]; - if ($this->actionType == self::TESTEMAIL) { - $this->sendToAddress = $argv[1]; - } - if ($this->actionType == self::DELETEJOB) { - $this->jobIdToDelete = $argv[1]; - } - if ($this->actionType == self::TESTAWSSTAT) { - $this->buildGuid = $argv[1]; - } - if ($this->actionType == self::GETPROJECTTOKEN) { - $this->projectId = $argv[1]; - } - if ($this->actionType == self::MOVEPROJECT) { - $this->projectId = $argv[1]; - } - } - - public function performAction() { - switch($this->actionType){ - case self::TESTEMAIL: - $this->actionTestEmail(); - break; - case self::GETCONFIG: - $this->actionGetConfig(); - break; - case self::FORCEUPLOAD: - $this->actionForceUploadBuilds(); - break; - case self::GETCOMPLETED: - $this->actionGetBuildsCompleted(); - break; - case self::GETREMAINING: - $this->actionGetBuildsRemaining(); - break; - case self::GETBUILDS: - $this->actionGetBuilds(); - break; - case self::DELETEJOB: - $this->actionDeleteJob(); - break; - case self::TESTAWSSTAT: - $this->actionTestAwsBuildStatus(); - break; - case self::TESTIAM: - $this->actionTestIam(); - break; - case self::GETPROJECTTOKEN: - $this->actionGetProjectToken(); - break; - case self::MOVEPROJECT: - $this->actionMoveProjectToS3(); - break; - case self::MOVEALLPROJECTS: - $this->actionMoveAllProjectsToS3(); - break; - case self::MIGRATEJOBS: - $this->actionMigrateJobs(); - break; - } - } - private function actionTestAwsBuildStatus() - { - echo "Testing Get Build Status" . PHP_EOL; - $codeBuild = new CodeBuild(); - $buildProcess = "build_scriptureappbuilder"; - $buildStatus = $codeBuild->getBuildStatus($this->buildGuid, $buildProcess); - $phase = $buildStatus['currentPhase']; - $status = $buildStatus['buildStatus']; - echo " phase: " . $phase . " status: " . $status .PHP_EOL; - if ($codeBuild->isBuildComplete($buildStatus)) - { - echo ' Build Complete' . PHP_EOL; - } else { - echo ' Build Incomplete' . PHP_EOL; - } - var_dump($buildStatus); - } - /** - * Test email action. Requires email adddress as parameter (Dev only) - */ - private function actionTestEmail() - { - $body = \Yii::$app->mailer->render('@common/mail/operations/Test/enduser-testmsg',[ - 'name' => "Whom it may concern", - 'crashPlanUrl' => "www.google.com", - ]); - $mail = new EmailQueue(); - $mail->to = $this->sendToAddress; - $mail->subject = 'New test message'; - $mail->html_body = $body; - if(!$mail->save()){ - echo "Failed to send email" . PHP_EOL; - } - } - private function actionTestIam() - { - $iam = new IAmWrapper(); - $arn = $iam->getRoleArn('build_app'); -// $cb = new CodeBuild(); -// $answer = $cb->projectExists('build_app'); -// $answer = $cb->createProject(); - } - /** - * Get Configuration (Dev only) - */ - private function actionGetConfig() - { - $prefix = Utils::getPrefix(); - echo "[$prefix] Get Configuration..." . PHP_EOL; - - $repoLocalPath = \Yii::$app->params['buildEngineRepoLocalPath']; - $scriptDir = \Yii::$app->params['buildEngineRepoScriptDir']; - $privateKey = \Yii::$app->params['buildEngineRepoPrivateKey']; - $repoUrl = \Yii::$app->params['buildEngineRepoUrl']; - $repoBranch = \Yii::$app->params['buildEngineRepoBranch']; - $userName = \Yii::$app->params['buildEngineGitUserName']; - $userEmail = \Yii::$app->params['buildEngineGitUserEmail']; - $sshUser = \Yii::$app->params['buildEngineGitSshUser'] ?: ""; - - $artifactsBucket = S3::getArtifactsBucket(); - - echo "Repo:". PHP_EOL." URL:$repoUrl". PHP_EOL." Branch:$repoBranch". PHP_EOL." Path:$repoLocalPath". PHP_EOL." Scripts:$scriptDir". PHP_EOL." Key:$privateKey". PHP_EOL." SshUser: $sshUser". PHP_EOL; - echo "Git:". PHP_EOL." Name:$userName". PHP_EOL." Email:$userEmail". PHP_EOL; - echo "Artifacts:". PHP_EOL." Bucket:$artifactsBucket". PHP_EOL; - } - /** - * Force the completed successful builds to upload the builds to S3. (Dev only) - * Note: This should only be used during development to test whether - * S3 configuration is correct. - */ - private function actionForceUploadBuilds() - { - $logger = new Appbuilder_logger("DevelopmentAction"); - foreach (Build::find()->each(50) as $build){ - if ($build->status == Build::STATUS_COMPLETED - && $build->result == JenkinsBuild::SUCCESS) - { - $jobName = $build->job->name(); - echo "Attempting to save Build: Job=$jobName, BuildNumber=$build->build_number". PHP_EOL; - $logBuildDetails = JenkinsUtils::getlogBuildDetails($build); - $logBuildDetails['NOTE: ']='Force the completed successful builds to upload the builds to S3.'; - $logBuildDetails['NOTE2: ']='Attempting to save Build.'; - $logger->appbuilderWarningLog($logBuildDetails); - $task = OperationQueue::SAVETOS3; - $build_id = $build->id; - OperationQueue::findOrCreate($task, $build_id, null); - } - } - } - /** - * Get completed build information. (Dev only) - * Note: This should only be used during development for diagnosis. - */ - private function actionGetBuildsCompleted() - { - $logger = new Appbuilder_logger("DevelopmentAction"); - foreach (Build::find()->where([ - 'status' => Build::STATUS_COMPLETED, - 'result' => JenkinsBuild::SUCCESS])->each(50) as $build){ - $jobName = $build->job->name(); - try { - $logBuildDetails = JenkinsUtils::getlogBuildDetails($build); - $logger->appbuilderWarningLog($logBuildDetails); - } catch (\Exception $e) { - $logException = [ - 'problem' => 'Build not found.', - 'jobName' => $jobName, - 'Number' => $build->build_guid, - ]; - $logger->appbuilderExceptionLog($logException, $e); - echo PHP_EOL . "Exception Job=$jobName, BuildNumber=$build->build_guid ". PHP_EOL ."....Not found ". PHP_EOL; - } - - } - } - /** - * Return the builds that have not completed. (Dev only) - * Note: This should only be used during developement for diagnosis. - */ - private function actionGetBuildsRemaining() - { - $logger = new Appbuilder_logger("DevelopmentAction"); - $prefix = Utils::getPrefix(); - echo "[$prefix] Remaining Builds...". PHP_EOL; - $complete = Build::STATUS_COMPLETED; - foreach (Build::find()->where("status!='$complete'")->each(50) as $build){ - $jobName = $build->job->name(); - try { - if ($build->build_number > 0) { - $log = $this->getlogJenkinsS3Details($build); - $logger->appbuilderInfoLog($log); - } - } catch (\Exception $e) { - $logException = [ - 'problem' => 'Build not found.', - 'jobName' => $jobName, - 'Number' => $build->build_number, - 'Status' => $build->status - ]; - $logger->appbuilderWarningLog($logException); - echo "Job=$jobName, Number=$build->build_number, Status=$build->status". PHP_EOL ."....Not found ". PHP_EOL; - } - } - } - /** - * Return all the builds. (Dev only) - * Note: This should only be used during developement for diagnosis. - */ - private function actionGetBuilds() - { - $logger = new Appbuilder_logger("DevelopmentAction"); - $prefix = Utils::getPrefix(); - echo "[$prefix] All Builds...". PHP_EOL; - foreach (Build::find()->each(50) as $build){ - $jobName = $build->job->name(); - $logBuildDetails = JenkinsUtils::getlogBuildDetails($build); - $logger->appbuilderWarningLog($logBuildDetails); - try { - if ($build->build_number > 0) { - $logJenkinsS3 = $this->getlogJenkinsS3Details($build); - $logger->appbuilderWarningLog($logJenkinsS3); - } - } catch (\Exception $e) { - $logException = [ - 'problem' => 'Jenkins build '.$build->build_number.' not found', - 'jobName' => $jobName - ]; - $logger->appbuilderWarningLog($logException); - echo 'Exception: in actionGetBuilds build-> Jenkins build '.$build->build_number.' not found' . " $jobName". PHP_EOL . PHP_EOL; - } - } - } - - private function actionDeleteJob() { - echo "Deleting job $this->jobIdToDelete".PHP_EOL; - $job = Job::findById($this->jobIdToDelete); - if (is_null($job)) { - echo "Job $this->jobIdToDelete not found".PHP_EOL; - } else if ($job->delete()) { - echo "Successfully deleted record".PHP_EOL; - } else { - echo "Failed to delete record".PHP_EOL; - } - } - private function actionGetProjectToken() { - echo "Getting token for project $this->projectId".PHP_EOL; - $project = Project::findById($this->projectId); - if ($project->isS3Project()) { - echo "Path: ". $project->getS3ProjectPath().PHP_EOL; - echo "Policy:".PHP_EOL.STS::getReadWritePolicy($project).PHP_EOL; - $sts = new STS(); - $token = $sts->getProjectAccessToken($project, "TEST"); - echo "URL: ". $project->url.PHP_EOL; - echo "export AWS_ACCESS_KEY_ID=".$token['AccessKeyId'].PHP_EOL; - echo "export AWS_SECRET_ACCESS_KEY=".$token['SecretAccessKey'].PHP_EOL; - echo "export AWS_SESSION_TOKEN=".$token['SessionToken'].PHP_EOL; - } - } - /*=============================================== logging ============================================*/ - /** - * get Jenkins and S3 details - * @param Build $build - * @return Array - */ - private function getlogJenkinsS3Details($build) - { - $jobName = $build->job->name(); - $log = [ - 'logType' => 'S3 details', - 'jobName' => $jobName - ]; - $log['request_id'] = $build->job->request_id; - - $jenkins = $this->jenkinsUtils->getJenkins(); - $jenkinsJob = $jenkins->getJob($build->job->name()); - $jenkinsBuild = $jenkinsJob->getBuild($build->build_number); - $buildResult = $jenkinsBuild->getResult(); - list($artifactUrls, $artifactRelativePaths) = $this->jenkinsUtils->getArtifactUrls($jenkinsBuild); - - $log['jenkins_buildResult'] = $buildResult; - $i = 1; - foreach (array_map(null, $artifactUrls, $artifactRelativePaths) as list($url, $path)) { - $log['jenkins_artifact_'.$i] = "S3: Path=$path, Url=$url"; - $i++; - } - - echo "Job=$jobName, Number=$build->build_number, Status=$build->status". PHP_EOL - . " Build: Result=$buildResult". PHP_EOL; - return $log; - } - - private function actionMoveProjectToS3() - { - $project = Project::findById($this->projectId); - $this->moveProjectToS3($project); - } - - private function actionMoveAllProjectsToS3() - { - $projects = Project::find()->all(); - foreach ($projects as $project) { - $this->moveProjectToS3($project); - } - } - private function moveProjectToS3($project) { - echo "Moving project $project->id to S3".PHP_EOL; - if (!$this->isProjectConversionCandidate($project)) { - echo "Project " . $project->project_name . " does not require conversion." . PHP_EOL; - } else { - $s3 = new S3(); - $s3Folder = $project->getS3Folder(); - echo $s3Folder . PHP_EOL; - $baseFolder = Utils::lettersNumbersHyphensOnly($project->getS3BaseFolder()); - echo $baseFolder . PHP_EOL; - $key = substr($s3Folder, 0, strrpos($s3Folder, '/')); - echo $key . PHP_EOL; - $bucket = S3::getProjectsBucket(); - if ($s3->doesObjectExist($bucket, $key, $baseFolder) == true) - { - echo "Folder exists on s3 already" . PHP_EOL; - } else { - $this->copyFolderFromCbToS3($project, $key, $s3); - $sshUrl = $project->url; - $project->setS3Project(); - $project->save(); - $this->updateJobs($sshUrl, $project->url); - } - } - echo "Move project complete" . PHP_EOL; - } - private function copyFolderFromCbToS3($project, $key, $s3) - { - // Test 2 URL: ssh://APKAIO63SIBNZAEHNXLA@git-codecommit.us-east-1.amazonaws.com/v1/repos/scriptureappbuilder-CHB-en-EnglishGreek03134 - // Job ID 1 - // Test 4 URL: ssh://APKAIO63SIBNZAEHNXLA@git-codecommit.us-east-1.amazonaws.com/v1/repos/scriptureappbuilder-DEM-CHB-en-Test0306 - // Job ID 8 - $baseFolder = Utils::lettersNumbersHyphensOnly($project->getS3BaseFolder()); - echo "Copying Project Files for " . $baseFolder . PHP_EOL; - $gitWrapper = new GitWrapper(); - $gitWrapper->setTimeout(600); - $bucket = S3::getProjectsBucket(); - Utils::deleteDir("/tmp/copy"); - $tmpFolderName = "/tmp/copy/" . $project->project_name; - mkdir($tmpFolderName, 0777, true); - $codecommit = new CodeCommit(); - $branch = "master"; - $repoUrl = $codecommit->getSourceSshURL($project->url); - echo "Cloning URL: $repoUrl" . PHP_EOL; - $gitWrapper->cloneRepository($repoUrl, $tmpFolderName); - $gitDir = $tmpFolderName . "/.git"; - Utils::deleteDir($gitDir); - $tmpS3FolderName = "/tmp/copy/" . $baseFolder; - echo "Renaming " . $tmpFolderName . "to " . $tmpS3FolderName . PHP_EOL; - rename($tmpFolderName, $tmpS3FolderName); - $keyPrefix = $key . "/"; - echo "Copying from " . $tmpS3FolderName . " to S3 " . $bucket . " " . $keyPrefix . " " . $baseFolder . PHP_EOL; - $s3->uploadFolder("/tmp/copy", $bucket, $keyPrefix); - Utils::deleteDir("/tmp/copy"); - } - private function isProjectConversionCandidate($project) - { - $isComplete = Project::STATUS_COMPLETED == $project->status; - $isSuccessful = Project::RESULT_SUCCESS == $project->result; - $retVal = $isComplete && $isSuccessful && !$project->isS3Project(); - return $retVal; - } - private function updateJobs($sshUrl, $s3Url) - { - $jobs = Job::find()->where('git_url = :git_url', - ['git_url'=>$sshUrl])->all(); - foreach ($jobs as $job) { - echo "Updating job id " . $job->id . PHP_EOL; - $job->git_url = $s3Url; - $job->save(); - } - } - private function actionMigrateJobs() - { - echo "Migrating Jobs" . PHP_EOL; - $guid = Utils::createGUID(); - echo "GUID: " . $guid . PHP_EOL; - $jobs = Job::find()->groupBy(['git_url'])->all(); - foreach ($jobs as $job) { - $this->migrateJob($job->git_url, $job->client_id); - } - echo "Migration complete" . PHP_EOL; - } - private function migrateJob($git_url, $client_id) - { - $this->migrateCreateProject($git_url, $client_id); - $count = 1; - $jobs = Job::find()->where('git_url = :git_url', - ['git_url'=>$git_url])->all(); - foreach ($jobs as $job) { - echo $count . "Migrating url " . $job->git_url . " client_id " . $job->client_id . PHP_EOL; - $count = $count + 1; - $guid = Utils::createGUID(); - $job->request_id = $guid; - $job->save(); - } - } - private function migrateCreateProject($git_url, $client_id) - { - $repo = substr($git_url, strrpos($git_url, '/') + 1); - $pos_group = strpos($repo, '-') + 1; - $app_id = substr($repo, 0, $pos_group - 1); - $rest = substr($repo, $pos_group); - $group = substr($rest, 0, strpos($rest, '-')); - $rest = substr($rest, strpos($rest, '-') + 1); - $lang = substr($rest, 0, strpos($rest, '-')); - $proj = substr($rest, strpos($rest, '-') + 1); - $proj = str_replace("-", " ", $proj); - if ($proj == '') { - echo "**** Bad project name - Using default ****" . PHP_EOL; - $proj = $lang .' Default'; - } - echo "app_id: " . $app_id . " group: " . $group . " lang: " . $lang . " project: [" . $proj . "]" . PHP_EOL; - $project = new Project(); - $project->status = Project::STATUS_COMPLETED; - $project->result = Project::RESULT_SUCCESS; - $project->url = $git_url; - $project->app_id = $app_id; - $project->client_id = $client_id; - $project->language_code = $lang; - $project->project_name = $proj; - $project->save(); - - } -} diff --git a/application/console/components/ManageBuildsAction.php b/application/console/components/ManageBuildsAction.php deleted file mode 100644 index 1d823205..00000000 --- a/application/console/components/ManageBuildsAction.php +++ /dev/null @@ -1,240 +0,0 @@ -cronController = $cronController; - } - public function performAction() - { - $prefix = Utils::getPrefix(); - $tokenSemaphore = sem_get(11); - $tokenValue = shm_attach(12, 100); - - if (!$this->try_lock($tokenSemaphore, $tokenValue)){ - echo "[$prefix] ManageBuildsAction: Semaphore Blocked" . PHP_EOL; - return; - } - echo "[$prefix] ManageBuilds Action start" . PHP_EOL; - try { - $logger = new Appbuilder_logger("ManageBuildsAction"); - $complete = Build::STATUS_COMPLETED; - foreach (Build::find()->where("status!='$complete'")->each(50) as $build){ - if ($build->status != Build::STATUS_EXPIRED) { - $logBuildDetails = Build::getlogBuildDetails($build); - $logger->appbuilderWarningLog($logBuildDetails); - } - // codecept_debug("Build: " . (string)$build->id); - switch ($build->status){ - case Build::STATUS_INITIALIZED: - $this->tryStartBuild($build); - break; - case Build::STATUS_ACTIVE: - $this->checkBuildStatus($build); - break; - } - } - } - catch (\Exception $e) { - echo "Caught exception".PHP_EOL; - echo $e->getMessage() .PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logger = new Appbuilder_logger("ManageBuildsAction"); - $logException = [ - 'problem' => 'Caught exception', - ]; - $logger->appbuilderExceptionLog($logException, $e); - } - finally { - $this->release($tokenSemaphore, $tokenValue); - } - } - - /** - * Try to start a build. If it starts, then update the database. - * @param Build $build - */ - private function tryStartBuild($build) - { - $logger = new Appbuilder_logger("ManageBuildsAction"); - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryStartBuild: Starting Build of ".$build->jobName(). PHP_EOL; - - // Find the repo and commit id to be built - $job = $build->job; - - // Don't start job if a job for this build is currently running - $builds = Build::findAllRunningByJobId((string)$build->job_id); - // codecept_debug("Count of active builds: " . (string)count($builds)); - if (count($builds) > 0) { - // codecept_debug("...is currentlyBuilding so wait"); - echo '...is currentlyBuilding so wait'. PHP_EOL; - return; - } - $gitUrl = $job->git_url; - // Check to see if codebuild project - $codeCommitProject = (substr( $gitUrl, 0, 6) === "ssh://"); - if ($codeCommitProject) { - // Left this block intact to make it easier to remove when codecommit is not supported - $codecommit = new CodeCommit(); - $branch = "master"; - $repoUrl = $codecommit->getSourceURL($gitUrl); - $commitId = $codecommit->getCommitId($gitUrl, $branch); - - $script = $this->cronController->renderPartial("scripts/appbuilders_build", [ - ]); - // Start the build - $codeBuild = new CodeBuild(); - $versionCode = $this->getVersionCode($job, $build) + 1; - $lastBuildGuid = $codeBuild->startBuild($repoUrl, (string)$commitId, $build, (string) $script, (string)$versionCode, $codeCommitProject); - if (!is_null($lastBuildGuid)){ - $build->build_guid = $lastBuildGuid; - $build->codebuild_url = CodeBuild::getCodeBuildUrl('build_app', $lastBuildGuid); - $build->console_text_url = CodeBuild::getConsoleTextUrl('build_app', $lastBuildGuid); - echo "[$prefix] Launched Build LastBuildNumber=$build->build_guid". PHP_EOL; - $build->status = Build::STATUS_ACTIVE; - $build->save(); - } - } - else { - $script = $this->cronController->renderPartial("scripts/appbuilders_s3_build", [ - ]); - // Start the build - $codeBuild = new CodeBuild(); - $commitId = ""; // TODO: Remove when git is removed - $versionCode = $this->getVersionCode($job, $build); - $lastBuildGuid = $codeBuild->startBuild($gitUrl, (string)$commitId, $build, (string) $script, (string)$versionCode, $codeCommitProject); - if (!is_null($lastBuildGuid)){ - $build->build_guid = $lastBuildGuid; - $build->codebuild_url = CodeBuild::getCodeBuildUrl('build_app', $lastBuildGuid); - $build->console_text_url = CodeBuild::getConsoleTextUrl('build_app', $lastBuildGuid); - echo "[$prefix] Launched Build LastBuildNumber=$build->build_guid". PHP_EOL; - $build->status = Build::STATUS_ACTIVE; - $build->save(); - } - } - - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryStartBuild: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = Build::getlogBuildDetails($build); - $logger->appbuilderExceptionLog($logException, $e); - $this->failBuild($build); - } - } - - /** - * - * @param Build $build - */ - private function checkBuildStatus($build){ - $logger = new Appbuilder_logger("ManageBuildsAction"); - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] checkBuildStatus: Check Build of ".$build->jobName(). PHP_EOL; - - $job = $build->job; - if ($job) { - $codeBuild = new CodeBuild(); - $buildStatus = $codeBuild->getBuildStatus((string)$build->build_guid, CodeBuild::getCodeBuildProjectName('build_app')); - $phase = $buildStatus['currentPhase']; - $status = $buildStatus['buildStatus']; - echo " phase: " . $phase . " status: " . $status .PHP_EOL; - if ($codeBuild->isBuildComplete($buildStatus)) - { - echo ' Build Complete' . PHP_EOL; - } else { - echo ' Build Incomplete' . PHP_EOL; - } - - if ($codeBuild->isBuildComplete($buildStatus)) { - $build->status = Build::STATUS_POSTPROCESSING; - $status = $codeBuild->getStatus($buildStatus); - switch($status){ - case CodeBuild::STATUS_FAILED: - case CodeBuild::STATUS_FAULT: - case CodeBuild::STATUS_TIMED_OUT: - $build->result = Build::RESULT_FAILURE; - $this->handleFailure($build); - break; - case CodeBuild::STATUS_STOPPED: - $build->result = Build::RESULT_ABORTED; - $this->handleFailure($build); - break; - case CodeBuild::STATUS_SUCCEEDED: - $task = OperationQueue::SAVETOS3; - $build_id = $build->id; - OperationQueue::findOrCreate($task, $build_id, "build"); - break; - } - } - if (!$build->save()){ - throw new \Exception("Unable to update Build entry, model errors: ".print_r($build->getFirstErrors(),true), 1450216434); - } - $log = Build::getlogBuildDetails($build); - $log['job id'] = $job->id; - $logger->appbuilderWarningLog($log); - echo "Job=$job->id, Build=$build->build_guid, Status=$build->status, Result=$build->result". PHP_EOL; - } - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] checkBuildStatus: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = Build::getlogBuildDetails($build); - $logger->appbuilderErrorLog($logException); - $this->failBuild($build); - } - } - - private function handleFailure($build) - { - $build->error = $build->cloudWatch(); - $build_id = $build->id; - $task = OperationQueue::SAVEERRORTOS3; - OperationQueue::findOrCreate($task, $build_id, "build"); - } - private function getVersionCode($job, $build) { - $id = $job->id; - $retval = $job->existing_version_code; - foreach (Build::find()->where([ - 'job_id' => $id, - 'status' => Build::STATUS_COMPLETED, - 'result' => "SUCCESS"])->each(50) as $build){ - if (($build->version_code) && ($build->version_code > $retval)) { - $retval = $build->version_code; - } - } - return $retval; - } - private function failBuild($build) { - $logger = new Appbuilder_logger("ManageBuildsAction"); - try { - $build->result = Build::RESULT_FAILURE; - $build->status = Build::STATUS_COMPLETED; - $build->save(); - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] failBuild Exception:" . PHP_EOL . (string)$e . PHP_EOL; - echo "Exception: " . $e->getMessage() . PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logException = $this->getlogBuildDetails($build); - $logger->appbuilderExceptionLog($logException, $e); - } - } -} diff --git a/application/console/components/ManageProjectsAction.php b/application/console/components/ManageProjectsAction.php deleted file mode 100644 index 4bc8ee76..00000000 --- a/application/console/components/ManageProjectsAction.php +++ /dev/null @@ -1,268 +0,0 @@ -iAmWrapper = \Yii::$container->get('iAmWrapper'); - } - public function performAction() - { - $tokenSemaphore = sem_get(30); - $tokenValue = shm_attach(31, 100); - - if (!$this->try_lock($tokenSemaphore, $tokenValue)){ - $prefix = Utils::getPrefix(); - echo "[$prefix] ManageProjectsAction: Semaphore Blocked" . PHP_EOL; - return; - } - try { - $logger = new Appbuilder_logger("ManageProjectsAction"); - $complete = Project::STATUS_COMPLETED; - foreach (Project::find()->where("status!='$complete'")->each(50) as $project){ - echo "cron/manage-projects: Project=$project->id, ". PHP_EOL; - $logProjectDetails = $this->getlogProjectDetails($project); - $logger->appbuilderWarningLog($logProjectDetails); - switch ($project->status){ - case Project::STATUS_INITIALIZED: - $this->tryCreateRepo($project, $logger); - break; - case Project::STATUS_DELETE_PENDING: - $this->tryDeleteRepo($project, $logger); - break; - } - } - } - catch (\Exception $e) { - echo "Caught exception".PHP_EOL; - echo $e->getMessage() .PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logger = new Appbuilder_logger("ManageProjectsAction"); - $logException = [ - 'problem' => 'Caught exception', - ]; - $logger->appbuilderExceptionLog($logException, $e); - } - finally { - $this->release($tokenSemaphore, $tokenValue); - } - } - /*=============================================== logging ============================================*/ - /** - * - * get release details for logging. - * @param Release $release - * @return Array - */ - public function getlogProjectDetails($project) - { - $projectName = $project->project_name; - $log = [ - 'projectName' => $projectName, - 'projectId' => $project->id - ]; - $log['Project-Status'] = $project->status; - $log['Project-Result'] = $project->result; - - echo "Project=$project->id, Status=$project->status, Result=$project->result". PHP_EOL; - - return $log; - } - /** - * - * @param Release $release - */ - private function tryCreateRepo($project, $logger) - { - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryCreateRepo: Starting creation of project ".$project->project_name. PHP_EOL; - - $project->status = Project::STATUS_ACTIVE; - $project->save(); - $repoName = $project->repoName(); - $user = $this->iAmWrapper->createAwsAccount($project->user_id); - $this->findOrCreateIamCodeCommitGroup($project); - $this->iAmWrapper->addUserToIamGroup($project->user_id, $project->groupName()); - $public_key = $this->iAmWrapper->addPublicSshKey($project->user_id, $project->publishing_key); - $publicKeyId = $public_key['SSHPublicKey']['SSHPublicKeyId']; - - /* - * Only attempt to create repo if one has not already been created for this project. - * This prevents creating a second repo if the project name has changed - */ - // TODO: THINK ABOUT HOW WE FIND OUT IF PROJECT REPO CREATED IF NECESSARY - if ( ! is_null($project->url)) { - if (!($project->url == "")){ - return; - } - } - $repo = $this->createRepo($repoName); - $repoSshUrl = $this->addUserToSshUrl($repo['repositoryMetadata']['cloneUrlSsh'], $publicKeyId); - echo "Username: ".$user['User']['UserName'].PHP_EOL; - echo "RepoName: ".$repo['repositoryMetadata']['repositoryName'].PHP_EOL; - echo "RepoUrl: ".$repoSshUrl.PHP_EOL; - echo "Arn: ".$repo['repositoryMetadata']['Arn'].PHP_EOL; - echo "Key ID: ".$publicKeyId.PHP_EOL; - $project->url = $repoSshUrl; - $project->status = Project::STATUS_COMPLETED; - $project->result = Project::RESULT_SUCCESS; - $project->save(); - - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryCreateRepo: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = $this->getlogProjectDetails($project); - $logger->appbuilderExceptionLog($logException, $e); - $project->error = "File: ".$e->getFile()." Line: ".$e->getLine()." ".$e->getMessage(); - $project->status = Project::STATUS_COMPLETED; - $project->result = Project::RESULT_FAILURE; - $project->save(); - } - } - private function tryDeleteRepo($project, $logger) - { - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryDeleteRepo: Starting Delete of project ".$project->project_name. PHP_EOL; - if (! is_null($project->url)){ - $project->status = Project::STATUS_DELETING; - $project->save(); - $repoName = $this->constructRepoName($project); - $CCClient = $this->iAmWrapper->getCodecommitClient(); - $CCClient->deleteRepository([ - 'repositoryName' => $repoName, - ]); - } - $project->delete(); - echo "[$prefix] tryDeleteRepo: Delete of project complete ". PHP_EOL; - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryDeleteRepo: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = $this->getlogProjectDetails($project); - $logger->appbuilderExceptionLog($logException, $e); - $project->error = "File: ".$e->getFile()." Line: ".$e->getLine()." ".$e->getMessage(); - $project->status = Project::STATUS_COMPLETED; - $project->result = Project::RESULT_FAILURE; - $project->save(); - } - } - /** - * Create IAM Group for organization for access management to repo - * @param string $groupName - * @param string $entityCode - * @return array - * @throws Aws\Iam\Exception\LimitExceededException - * @throws Aws\Iam\Exception\EntityAlreadyExistsException - * @throws Aws\Iam\Exception\NoSuchEntityException - * @throws Aws\Iam\Exception\ServiceFailureException - * @throws Aws\Iam\Exception\MalformedPolicyDocumentException - */ - private function findOrCreateIamCodeCommitGroup($project) - { - $iamClient = $this->iAmWrapper->getIamClient(); - $groupName = $project->groupName(); - $entityCode = $project->entityName(); - try { - $iamClient->getGroup([ - 'GroupName' => $groupName, - ]); - } catch (NoSuchEntityException $e) { - $this->createIamCodeCommitGroup($iamClient, $groupName, $entityCode, $project->app_id); - } catch (IamException $e) { - if ($e->getAwsErrorCode() == "NoSuchEntity") { - $this->createIamCodeCommitGroup($iamClient, $groupName, $entityCode, $project->app_id); - } - } - } - private function createIamCodeCommitGroup($iamClient, $groupName, $entityCode, $app_id){ - /** - * Group doesn't exist yet, so create it and attach policy - */ - $awsUserId = \Yii::$app->params['awsUserId']; - $awsRegion = \Yii::$app->params['awsRegion']; - $result = $iamClient->createGroup([ - 'Path' => '/', - 'GroupName' => $groupName, - ]); - $policy = [ - 'Version' => '2012-10-17', - 'Statement' => [ - [ - 'Effect' => 'Allow', - 'Action' => [ - 'codecommit:GitPull', - 'codecommit:GitPush' - ], - 'Resource' => [ - 'arn:aws:codecommit:'.$awsRegion.':'.$awsUserId.':'.$app_id.'-'.$entityCode.'-*' - ] - ] - ] - ]; - $result = $iamClient->putGroupPolicy([ - 'GroupName' => $groupName, - 'PolicyName' => $groupName, - 'PolicyDocument' => Json::encode($policy), - ]); - } - - /** - * @param $repoName - * @return \Guzzle\Service\Resource\Model - * @throws ServerErrorHttpException - * @internal param $data - */ - private function createRepo($repoName) - { - $CCClient = $this->iAmWrapper->getCodecommitClient(); - - try { - $result = $CCClient->createRepository([ - 'repositoryDescription' => 'Scripture App Builder Repository for '.$repoName, - 'repositoryName' => $repoName, - ]); - - - return $result; - } catch (IamException $e) { - if($e->getAwsErrorCode()=='RepositoryNameExists') { - - $message = sprintf( - 'SAB: Unable to create new repo - name already exists: %s. code=%s : message=%s', - $repoName, - $e->getCode(), - $e->getMessage() - ); - throw new ServerErrorHttpException($message, 1437423427, $e); - } - - throw $e; - } - } - private function addUserToSshUrl($sshUrl, $user) - { - return str_replace('ssh://', 'ssh://' . $user . '@', $sshUrl); - } -} - diff --git a/application/console/components/ManageReleasesAction.php b/application/console/components/ManageReleasesAction.php deleted file mode 100644 index d6044520..00000000 --- a/application/console/components/ManageReleasesAction.php +++ /dev/null @@ -1,232 +0,0 @@ -cronController = $cronController; - } - public function performAction() - { - $tokenSemaphore = sem_get(8); - $tokenValue = shm_attach(9, 100); - - if (!$this->try_lock($tokenSemaphore, $tokenValue)){ - $prefix = Utils::getPrefix(); - echo "[$prefix] ManageReleasesAction: Semaphore Blocked" . PHP_EOL; - return; - } - - try { - $logger = new Appbuilder_logger("ManageReleasesAction"); - $complete = Release::STATUS_COMPLETED; - foreach (Release::find()->where("status!='$complete'")->each(50) as $release){ - $build = $release->build; - $job = $build->job; - echo "cron/manage-releases: Job=$job->id, ". PHP_EOL; - $logReleaseDetails = $this->getlogReleaseDetails($release); - $logger->appbuilderWarningLog($logReleaseDetails); - switch ($release->status){ - case Release::STATUS_INITIALIZED: - $this->tryStartRelease($release); - break; - case Release::STATUS_ACTIVE: - $this->checkReleaseStatus($release); - break; - } - } - } - catch (\Exception $e) { - echo "Caught exception".PHP_EOL; - echo $e->getMessage() .PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logger = new Appbuilder_logger("ManageBuildsAction"); - $logException = [ - 'problem' => 'Caught exception', - ]; - $logger->appbuilderExceptionLog($logException, $e); - } - finally { - $this->release($tokenSemaphore, $tokenValue); - } - } - /*=============================================== logging ============================================*/ - /** - * - * get release details for logging. - * @param Release $release - * @return Array - */ - public function getlogReleaseDetails($release) - { - $build = $release->build; - $job = $build->job; - - $jobName = $build->job->name(); - $log = [ - 'jobName' => $jobName, - 'jobId' => $job->id - ]; - $log['Release-id'] = $release->id; - $log['Release-Status'] = $release->status; - $log['Release-Build'] = (string) $release->build_guid; - $log['Release-Result'] = $release->result; - - echo "Release=$release->id, Build=$release->build_guid, Status=$release->status, Result=$release->result". PHP_EOL; - - return $log; - } - /** - * - * @param Release $release - */ - private function tryStartRelease($release) - { - $logger = new Appbuilder_logger("ManageReleasesAction"); - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryStartRelease: Starting Build of ".$release->jobName()." for Channel ".$release->channel. PHP_EOL; - - $build = $release->build; - $artifactUrl = $build->apk(); - $path = $build->artifact_url_base; - - $script = $this->cronController->renderPartial("scripts/appbuilders_publish", [ - ]); - - // Start the build - $codeBuild = new CodeBuild(); - $lastBuildGuid = $codeBuild->startRelease($release, (string) $script); - if (!is_null($lastBuildGuid)){ - $release->build_guid = $lastBuildGuid; - $release->codebuild_url = CodeBuild::getCodeBuildUrl('publish_app', $lastBuildGuid); - $release->console_text_url = CodeBuild::getConsoleTextUrl('publish_app', $lastBuildGuid); - echo "[$prefix] Launched Build LastBuildNumber=$release->build_guid". PHP_EOL; - $release->status = Release::STATUS_ACTIVE; - $release->save(); - } - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] tryStartRelease: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - $logException = $this->getlogReleaseDetails($release); - $logger->appbuilderExceptionLog($logException, $e); - } - } - /** - * @param Release $release - */ - private function checkReleaseStatus($release) - { - $logger = new Appbuilder_logger("ManageBuildsAction"); - try { - $prefix = Utils::getPrefix(); - echo "[$prefix] checkReleaseStatus: Checking Build of ".$release->jobName()." for Channel ".$release->channel. PHP_EOL; - - $build = $release->build; - echo "Build id : " . $build->id . PHP_EOL; - $job = $build->job; - if ($job) { - $codeBuild = new CodeBuild(); - - $buildStatus = $codeBuild->getBuildStatus((string)$release->build_guid, CodeBuild::getCodeBuildProjectName('publish_app')); - $phase = $buildStatus['currentPhase']; - $status = $buildStatus['buildStatus']; - echo " phase: " . $phase . " status: " . $status .PHP_EOL; - if ($codeBuild->isBuildComplete($buildStatus)) - { - echo ' Build Complete' . PHP_EOL; - } else { - echo ' Build Incomplete' . PHP_EOL; - } - - if ($codeBuild->isBuildComplete($buildStatus)) { - $release->status = Release::STATUS_POSTPROCESSING; - $status = $codeBuild->getStatus($buildStatus); - switch($status){ - case CodeBuild::STATUS_FAILED: - case CodeBuild::STATUS_FAULT: - case CodeBuild::STATUS_TIMED_OUT: - $release->result = Build::RESULT_FAILURE; - $this->handleFailure($release); - break; - case CodeBuild::STATUS_STOPPED: - $release->result = Build::RESULT_ABORTED; - $this->handleFailure($release); - break; - case CodeBuild::STATUS_SUCCEEDED: - $release->result = Build::RESULT_SUCCESS; - $build->channel = $release->channel; - $build->save(); - $task = OperationQueue::SAVETOS3; - $build_id = $build->id; - OperationQueue::findOrCreate($task, $release->id, "release"); - break; - } - } - if (!$release->save()){ - throw new \Exception("Unable to update Build entry, model errors: ".print_r($release->getFirstErrors(),true), 1452611606); - } - $log = $this->getlogReleaseDetails($release); - $logger->appbuilderWarningLog($log); - } - } catch (\Exception $e) { - $prefix = Utils::getPrefix(); - echo "[$prefix] checkBuildStatus: Exception:" . PHP_EOL . (string)$e . PHP_EOL; - echo "Exception: " . $e->getMessage() . PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logException = $this->getlogReleaseDetails($release); - $logger->appbuilderExceptionLog($logException, $e); - $this->failRelease($release); - } - - - } - private function handleFailure($release) - { - $release->error = $release->cloudWatch(); - $release_id = $release->id; - $task = OperationQueue::SAVEERRORTOS3; - OperationQueue::findOrCreate($task, $release_id, "release"); - } - - private function getBuild($id) - { - $build = Build::findOne(['id' => $id]); - if (!$build){ - echo "Build not found ". PHP_EOL; - throw new NotFoundHttpException(); - } - return $build; - } - private function failRelease($release) { - try { - $release->result = Build::RESULT_FAILURE; - $release->status = Release::STATUS_COMPLETED; - $release->save(); - } catch (\Exception $e) { - $logger = new Appbuilder_logger("ManageReleasesAction"); - $prefix = Utils::getPrefix(); - echo "[$prefix] failRelease Exception:" . PHP_EOL . (string)$e . PHP_EOL; - echo "Exception: " . $e->getMessage() . PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logException = $this->getlogReleaseDetails($release); - $logger->appbuilderExceptionLog($logException, $e); - } - } -} diff --git a/application/console/components/MaxRetriesExceededException.php b/application/console/components/MaxRetriesExceededException.php deleted file mode 100644 index 8e233745..00000000 --- a/application/console/components/MaxRetriesExceededException.php +++ /dev/null @@ -1,16 +0,0 @@ -verbose = $verbose; - // Set the maximum number of entries that may be attempted in one run - $this->batchSize = 10; - $this->successfulJobs = 0; - $this->failedJobs = 0; - $this->iterationsRun = 0; - $this->maxRetriesExceeded = 0; - } - /** - * Process operation_queue - * This function will iterate up to the configured limit and for each iteration - * it will process the next job in queue - */ - public function performAction() - { - $prefix = Utils::getPrefix(); - -/* $tokenSemaphore = sem_get(20); - $tokenValue = shm_attach(21, 100); - if (!$this->try_lock($tokenSemaphore, $tokenValue)){ - echo "[$prefix] OperationQueueAction: Semaphore Blocked" . PHP_EOL; - return; - } -*/ - echo "[$prefix] OperationQueue: Action start" . PHP_EOL; - try { - // Capture start time for log - $starttimestamp = time(); - $queuedJobs = OperationQueue::find()->count(); - // Do the work - for($i=0; $i<$queuedJobs; $i++){ - if (!$this->processNextJob()) { - // No more jobs to process - break; - } - $attempts = $this->successfulJobs + $this->failedJobs; - if ($attempts >= $this->batchSize) { - break; - } - } - $this->logResults($starttimestamp, $queuedJobs); - } - catch (\Exception $e) { - echo "Caught exception".PHP_EOL; - echo $e->getMessage() .PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logger = new Appbuilder_logger("OperationQueueAction"); - $logException = [ - 'problem' => 'Caught exception', - ]; - $logger->appbuilderExceptionLog($logException, $e); - } - finally { -// $this->release($tokenSemaphore, $tokenValue); - } - } - private function processNextJob() - { - $retVal = true; - $this->iterationsRun++; - try{ - $results = OperationQueue::processNext(null); - if($results) { - $this->successfulJobs++; - } else { - $retVal = false; - } - } - catch (MaxRetriesExceededException $e) { - // Don't count entries in the database that are now obsolete - // and are never deleted - echo "Caught max retry exception".PHP_EOL; - $logger = new Appbuilder_logger("OperationsQueueAction"); - $logException = [ - 'problem' => 'Maximum retries exceeded', - ]; - $logger->appbuilderExceptionLog($logException, $e); - $this->maxRetriesExceeded++; - } - - catch (\Exception $e) { - echo "Caught another exception".PHP_EOL; - echo $e->getMessage() .PHP_EOL; - echo $e->getFile() . PHP_EOL; - echo $e->getLine() . PHP_EOL; - $logger = new Appbuilder_logger("OperationsQueueAction"); - $logException = [ - 'problem' => 'Caught another exception', - ]; - $logger->appbuilderExceptionLog($logException, $e); - $this->failedJobs++; - } - return $retVal; - } - private function logResults($starttimestamp, $queuedJobs) - { - $logger = new Appbuilder_logger("OperationsQueueAction"); - // Capture endtime for log - $endtimestamp = time(); - $totaltime = $endtimestamp-$starttimestamp; - - $logMsg = 'cron/operation-queue - queued='.$queuedJobs.' successful='.$this->successfulJobs.' failed='.$this->failedJobs; - $logMsg .= ' retries exceeded='.$this->maxRetriesExceeded.' iterations='.$this->iterationsRun.' totaltime='.$totaltime; - $logArray = [$logMsg]; - if($this->failedJobs > 0){ - $logger->appbuilderErrorLog($logArray); - } else{ - if($this->verbose && $this->verbose != 'false'){ - $logger->appbuilderWarningLog($logArray); - } else { - $logger->appbuilderInfoLog($logArray); - } - } - - echo PHP_EOL . $logMsg . PHP_EOL; - } -} \ No newline at end of file diff --git a/application/console/components/ProjectUpdateOperation.php b/application/console/components/ProjectUpdateOperation.php deleted file mode 100644 index 6590d937..00000000 --- a/application/console/components/ProjectUpdateOperation.php +++ /dev/null @@ -1,90 +0,0 @@ -id = $id; - $this->parms = $parms; - $this->iAmWrapper = \Yii::$container->get('iAmWrapper'); - } - public function performOperation() - { - $prefix = Utils::getPrefix(); - echo "[$prefix] ProjectUpdateOperation ID: " . $this->id . PHP_EOL; - $project = Project::findByIdFiltered($this->id);; - if ($project) { - echo "Found record" . PHP_EOL; - $parmsArray = explode(',', $this->parms); - $publishing_key = $parmsArray[0]; - $user_id = $parmsArray[1]; - $this->checkRemoveUserFromGroup($project); - $this->updateProject($project, $user_id, $publishing_key); - } else { - echo "Didn't find record" . PHP_EOL; - } - } - public function getMaximumRetries() - { - return $this->maxRetries; - } - public function getMaximumDelay() - { - return $this->maxDelay; - } - public function getAlertAfterAttemptCount() - { - return $this->alertAfter; - } - /** - * If the user/group combination associated with the current project is - * the only project that exists, then remove the IAM user from the IAM group - * - * @param Project $project - * @return void - */ - private function checkRemoveUserFromGroup($project) - { - echo "checkRemoveUserFromGroup" . PHP_EOL; - $projects = Project::find()->where('user_id = :user_id and group_id = :group_id', - ['user_id'=>$project->user_id, 'group_id'=>$project->group_id])->all(); - $projectCount = count($projects); - if (count($projects) < 2) { - // Remove the user from the group - echo "CheckRemoveUserFromGroup: Removing [{$project->user_id}] from group [{$project->groupName()}]" . PHP_EOL; - $this->iAmWrapper->removeUserFromIamGroup($project->user_id, $project->groupName()); - } - } - private function updateProject($project, $user_id, $publishing_key) - { - echo "updateProject" . PHP_EOL; - $project->user_id = $user_id; - $project->publishing_key = $publishing_key; - $user = $this->iAmWrapper->createAwsAccount($user_id); - $this->iAmWrapper->addUserToIamGroup($project->user_id, $project->groupName()); - $public_key = $this->iAmWrapper->addPublicSshKey($project->user_id, $project->publishing_key); - $publicKeyId = $public_key['SSHPublicKey']['SSHPublicKeyId']; - $project->url = $this->adjustUrl($project->url, $publicKeyId); - $project->save(); - } - private function adjustUrl($url, $newPublicKeyId) - { - $segments = explode('@', $url); - return str_replace($segments[0], 'ssh://' . $newPublicKeyId, $url); - } -} \ No newline at end of file diff --git a/application/console/components/RemoveExpiredBuildsAction.php b/application/console/components/RemoveExpiredBuildsAction.php deleted file mode 100644 index 89ee0348..00000000 --- a/application/console/components/RemoveExpiredBuildsAction.php +++ /dev/null @@ -1,39 +0,0 @@ -where([ - 'status' => Build::STATUS_EXPIRED])->each(50) as $build){ - if ($build->apk() != null) { - echo "...Remove expired job $build->job_id id $build->id ". PHP_EOL; - $s3 = new S3(); - $s3->removeS3Artifacts($build); - $build->clearArtifacts(); - $logBuildDetails = Build::getlogBuildDetails($build); - $logBuildDetails['NOTE: ']='Remove expired S3 Artifacts for an expired build.'; - $logger->appbuilderWarningLog($logBuildDetails); - } - } - echo "[$prefix] actionRemoveExpiredBuilds: Conpleted". PHP_EOL; - - } - public function getS3() - { - return $this->s3; - } -} diff --git a/application/console/components/S3MaintenanceAction.php b/application/console/components/S3MaintenanceAction.php deleted file mode 100644 index c4de0ada..00000000 --- a/application/console/components/S3MaintenanceAction.php +++ /dev/null @@ -1,30 +0,0 @@ -s3 = new S3(); - $logStringArray = $this->s3->removeS3FoldersWithoutJobRecord($jobNames); - $logger->appbuilderWarningLog($logStringArray); - - } - public function getS3() - { - return $this->s3; - } -} diff --git a/application/console/config/.gitignore b/application/console/config/.gitignore deleted file mode 100644 index 20da318c..00000000 --- a/application/console/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/application/console/config/bootstrap.php b/application/console/config/bootstrap.php deleted file mode 100644 index b3d9bbc7..00000000 --- a/application/console/config/bootstrap.php +++ /dev/null @@ -1 +0,0 @@ - 'app-console', - 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log', 'gii'], - 'controllerNamespace' => 'console\controllers', - 'modules' => [ - 'gii' => [ - 'class' => 'yii\gii\Module', - 'allowedIPs' => ['127.0.0.1', '::1', '192.168.0.*', '192.168.70.1'] // adjust this to your needs - ], - ], - 'components' => [ - 'log' => [ - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - ], - 'params' => [ - 'buildEngineGitUserName' => $BUILD_ENGINE_GIT_USER_NAME, - 'buildEngineGitUserEmail' => $BUILD_ENGINE_GIT_USER_EMAIL, - 'buildEngineGitSshUser' => $BUILD_ENGINE_GIT_SSH_USER, - 'buildEngineRepoUrl' => $BUILD_ENGINE_REPO_URL, - 'buildEngineRepoBranch' => $BUILD_ENGINE_REPO_BRANCH, - 'buildEngineRepoPrivateKey' => $BUILD_ENGINE_REPO_PRIVATE_KEY, - 'buildEngineRepoLocalPath' => $BUILD_ENGINE_REPO_LOCAL_PATH, - 'buildEngineRepoScriptDir' => $BUILD_ENGINE_REPO_SCRIPT_DIR, - 'buildEngineJenkinsMasterUrl' => $BUILD_ENGINE_JENKINS_MASTER_URL, - 'buildEngineArtifactsBucket' => $BUILD_ENGINE_ARTIFACTS_BUCKET, - 'buildEngineArtifactsBucketRegion' => $BUILD_ENGINE_ARTIFACTS_BUCKET_REGION, - 'buildEngineProjectsBucket' => $BUILD_ENGINE_PROJECTS_BUCKET, - 'buildEngineSecretsBucket' => $BUILD_ENGINE_SECRETS_BUCKET, - 'codeBuildImageRepo' => $CODE_BUILD_IMAGE_REPO, - 'codeBuildImageTag' => $CODE_BUILD_IMAGE_TAG, - 'publishJenkinsMasterUrl' => $PUBLISH_JENKINS_MASTER_URL, - 'appBuilderGitSshUser' => $APPBUILDER_GIT_SSH_USER, - 'awsKeyId' => $AWS_ACCESS_KEY_ID, - 'awsSecretKey' => $AWS_SECRET_ACCESS_KEY, - 'awsRegion' => $AWS_REGION, - 'awsUserId' => $AWS_USER_ID, - 'scriptureEarthKey' => $SCRIPTURE_EARTH_KEY, - ], - 'controllerMap' => [ - 'fixture' => [ - 'class' => 'yii\faker\FixtureController', - 'fixtureDataPath' => '@tests/codeception/common/fixtures/data', - 'templatePath' => '@tests/codeception/common/templates/fixtures', - 'namespace' => 'tests\codeception\common\fixtures', - ], - ], -]; diff --git a/application/console/config/params.php b/application/console/config/params.php deleted file mode 100644 index 7f754b91..00000000 --- a/application/console/config/params.php +++ /dev/null @@ -1,4 +0,0 @@ - 'admin@example.com', -]; diff --git a/application/console/controllers/.gitkeep b/application/console/controllers/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/application/console/controllers/CronController.php b/application/console/controllers/CronController.php deleted file mode 100644 index a2d284d1..00000000 --- a/application/console/controllers/CronController.php +++ /dev/null @@ -1,244 +0,0 @@ -set('fileUtils', 'common\components\FileUtils'); - \Yii::$container->set('gitWrapper', 'GitWrapper\GitWrapper'); - \Yii::$container->set('iAmWrapper', 'common\components\IAmWrapper'); - parent::__construct($id, $module, $config); - } - - public function actionAwsStartup() - { - $awsStartup = new AwsStartupAction(); - $awsStartup->performAction(); - } - - /** - * Manage the state of the builds and process the current state - * until the status is complete. - */ - public function actionManageBuilds() - { - $manageBuildsAction = new ManageBuildsAction($this); - $manageBuildsAction->performAction(); - } - - /** - * Manage the state of the releases and process the current state - * until the status is complete. - */ - public function actionManageReleases() - { - $manageReleasesAction = new ManageReleasesAction($this); - $manageReleasesAction->performAction(); - } - /** - * Send queued emails - */ - public function actionSendEmails($verbose=false) - { - $emailCount = EmailQueue::find()->count(); - - if($emailCount && is_numeric($emailCount)){ - echo "cron/send-emails - Count: " . $emailCount . ". ". PHP_EOL; - } - - list($emails_sent, $errors) = EmailUtils::sendEmailQueue(); - if (count($emails_sent) > 0) { - echo "... sent=".count($emails_sent). PHP_EOL; - } - if ($errors && count($errors) > 0) { - echo '... errors='.count($errors).' messages=['.join(',',$errors).']' . PHP_EOL; - } - } - - /** - * Remove expired builds from S3 - */ - public function actionRemoveExpiredBuilds() - { - $removeExpiredBuildsAction = new RemoveExpiredBuildsAction(); - $removeExpiredBuildsAction->performAction(); - } - - /** - * Delete orphaned files on S3 - */ - public function actionS3Maintenance() - { - $s3MaintenanceAction = new S3MaintenanceAction(); - $s3MaintenanceAction->performAction(); - } - /** - * Process operation_queue - * This function will iterate up to the configured limit and for each iteration - * it will process the next job in queue - */ - public function actionOperationQueue($verbose=false) - { - $operationsQueueAction = new OperationsQueueAction($verbose); - $operationsQueueAction->performAction(); - } - /** - * Test email action. Requires email adddress as parameter (Dev only) - */ - public function actionTestEmail($sendToAddress) - { - $developmentAction = new DevelopmentAction(DevelopmentAction::TESTEMAIL, $sendToAddress); - $developmentAction->performAction(); - } - - /** - * Test AWS Build Status function (Dev Only) - * Requires build guid as parameter - */ - public function actionTestBuildStatus($buildGuid) - { - $developmentAction = new DevelopmentAction(DevelopmentAction::TESTAWSSTAT, $buildGuid); - $developmentAction->performAction(); - } - /** - * Get Configuration (Dev only) - */ - public function actionGetConfig() - { - $developmentAction = new DevelopmentAction(DevelopmentAction::GETCONFIG); - $developmentAction->performAction(); - } - /** - * Return all the builds. (Dev only) - * Note: This should only be used during developement for diagnosis. - */ - public function actionGetBuilds() - { - $developmentAction = new DevelopmentAction(DevelopmentAction::GETBUILDS); - $developmentAction->performAction(); - } - /** - * Return the builds that have not completed. (Dev only) - * Note: This should only be used during developement for diagnosis. - */ - public function actionGetBuildsRemaining() - { - $developmentAction = new DevelopmentAction(DevelopmentAction::GETREMAINING); - $developmentAction->performAction(); - } - /** - * Get completed build information. (Dev only) - * Note: This should only be used during development for diagnosis. - */ - public function actionGetBuildsCompleted() - { - $developmentAction = new DevelopmentAction(DevelopmentAction::GETCOMPLETED); - $developmentAction->performAction(); - } - /** - * Force the completed successful builds to upload the builds to S3. (Dev only) - * Note: This should only be used during development to test whether - * S3 configuration is correct. - */ - public function actionForceUploadBuilds() - { - $developmentAction = new DevelopmentAction(DevelopmentAction::FORCEUPLOAD); - $developmentAction->performAction(); - } - /** - * Delete job. Job ID required as parameter - */ - public function actionDeleteJob($jobIdToDelete) - { - $developmentAction = new DevelopmentAction(DevelopmentAction::DELETEJOB, $jobIdToDelete); - $developmentAction->performAction(); - } - /** - * Set initial version code. Job ID and the initial version code required as parameters - * Example: ./yii cron/set-initial-version-code 1, 33 - */ - public function actionSetInitialVersionCode($jobId, $initialVersionCode) - { - $developmentAction = new SetInitialVersionCodeAction($jobId, $initialVersionCode); - $developmentAction->performAction(); - } - public function actionCheckCount() - { - $count = Job::recordCount(); - echo "Count:[$count]".PHP_EOL; - } - /** - * Return information for project token. (Dev only) - * Example: ./yii cron/project-token 1 - * Note: This should only be used during developement for diagnosis. - */ - public function actionProjectToken($projectId) - { - $developmentAction = new DevelopmentAction(DevelopmentAction::GETPROJECTTOKEN, $projectId); - $developmentAction->performAction(); - } - - /** - * Convert single project and associated jobs from ssh to S3. (Dev only) - * Example: ./yii cron/move-project-to-s3 2 - * Note: Copies project files from ssh url to s3 bucket - * Requires SSH Key of docker image to be registered with IAM user referenced by local.env - * Requires that IAM User being used has policy that allows access to codebuild project folders - * Changes URLs of project and jobs to point to S3 bucket - */ - public function actionMoveProjectToS3($projectId) - { - $developmentAction = new DevelopmentAction(DevelopmentAction::MOVEPROJECT, $projectId); - $developmentAction->performAction(); - } - /** - * Converts all successful projects with ssh url to S3 (Dev only) - * Calls the actionMoveProjectToS3 for all existing projects - * Only successful ssh projects will actually be affected - * Same requirements apply for this as for the actionMoveProjectsToS3 - */ - public function actionMoveAllProjectsToS3() - { - $developmentAction = new DevelopmentAction(DevelopmentAction::MOVEALLPROJECTS); - $developmentAction->performAction(); - } - public function actionMigrateJobs() - { - $developmentAction = new DevelopmentAction(DevelopmentAction::MIGRATEJOBS); - $developmentAction->performAction(); - } - /** - * Manage the state of the creation of a project repo - * until the status is complete. - */ - public function actionManageProjects() - { - $manageProjectsAction = new ManageProjectsAction(); - $manageProjectsAction->performAction(); - } - -} diff --git a/application/console/migrations-local/.gitignore b/application/console/migrations-local/.gitignore deleted file mode 100644 index c96a04f0..00000000 --- a/application/console/migrations-local/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/application/console/migrations/.gitkeep b/application/console/migrations/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/application/console/migrations/m150903_174303_create_job_table.php b/application/console/migrations/m150903_174303_create_job_table.php deleted file mode 100644 index 3fad4430..00000000 --- a/application/console/migrations/m150903_174303_create_job_table.php +++ /dev/null @@ -1,39 +0,0 @@ -createTable('{{job}}', [ - 'id' => Schema::TYPE_PK, - 'request_id' => Schema::TYPE_INTEGER . " NOT NULL", - 'git_url' => 'varchar(2083) NOT NULL', - 'app_id'=> Schema::TYPE_STRING . " NOT NULL", - 'publisher_id'=> Schema::TYPE_STRING . " NOT NULL", - - 'created' => 'datetime null', - 'updated' => 'datetime null', - ],"ENGINE=InnoDB DEFAULT CHARSET=utf8"); - $this->createIndex("idx_request_id", "{{job}}", "request_id"); - } - - public function down() - { - $this->dropIndex("idx_request_id", "{{job}}"); - $this->dropTable("{{job}}"); - } - - /* - // Use safeUp/safeDown to run migration code within a transaction - public function safeUp() - { - } - - public function safeDown() - { - } - */ -} diff --git a/application/console/migrations/m150914_144408_create_build_table.php b/application/console/migrations/m150914_144408_create_build_table.php deleted file mode 100644 index 83e3856b..00000000 --- a/application/console/migrations/m150914_144408_create_build_table.php +++ /dev/null @@ -1,41 +0,0 @@ -createTable('{{build}}', [ - 'id' => Schema::TYPE_PK, - 'job_id' => 'int(11) not null', - 'status' => Schema::TYPE_STRING . " null", - 'build_number'=> Schema::TYPE_INTEGER . " null", - 'build_result' => Schema::TYPE_STRING . " null", - 'build_error' => Schema::TYPE_STRING . " null", - 'artifact_url'=> 'varchar(2083) null', - - 'created' => 'datetime null', - 'updated' => 'datetime null', - ],"ENGINE=InnoDB DEFAULT CHARSET=utf8"); - $this->addForeignKey('fk_build_job_id','{{build}}','job_id', - '{{job}}','id','NO ACTION','NO ACTION'); - } - - public function down() - { - $this->dropTable("{{build}}"); - } - - /* - // Use safeUp/safeDown to run migration code within a transaction - public function safeUp() - { - } - - public function safeDown() - { - } - */ -} diff --git a/application/console/migrations/m150917_163901_add_artifact_url_base_to_job.php b/application/console/migrations/m150917_163901_add_artifact_url_base_to_job.php deleted file mode 100644 index fa027ad1..00000000 --- a/application/console/migrations/m150917_163901_add_artifact_url_base_to_job.php +++ /dev/null @@ -1,16 +0,0 @@ -addColumn("{{job}}", "artifact_url_base", "varchar(1024) null"); - } - - public function down() - { - $this->dropColumn("{{job}}", "artifact_url_base"); - } -} diff --git a/application/console/migrations/m151005_142150_rename_build_columns.php b/application/console/migrations/m151005_142150_rename_build_columns.php deleted file mode 100644 index 639a0bec..00000000 --- a/application/console/migrations/m151005_142150_rename_build_columns.php +++ /dev/null @@ -1,19 +0,0 @@ -renameColumn("{{build}}", "build_result", "result"); - $this->renameColumn("{{build}}", "build_error", "error"); - } - - public function down() - { - $this->renameColumn("{{build}}", "result", "build_result"); - $this->renameColumn("{{build}}", "error", "build_error"); - } -} diff --git a/application/console/migrations/m151106_195737_change_job_request_id_type.php b/application/console/migrations/m151106_195737_change_job_request_id_type.php deleted file mode 100644 index 418e1833..00000000 --- a/application/console/migrations/m151106_195737_change_job_request_id_type.php +++ /dev/null @@ -1,17 +0,0 @@ -alterColumn('{{job}}', 'request_id', Schema::TYPE_STRING . " NOT NULL"); - } - - public function down() - { - $this->alterColumn('{{job}}', 'request_id', Schema::TYPE_INTEGER . " NOT NULL"); - } -} diff --git a/application/console/migrations/m151117_175414_add_channel_to_build.php b/application/console/migrations/m151117_175414_add_channel_to_build.php deleted file mode 100644 index 5153606a..00000000 --- a/application/console/migrations/m151117_175414_add_channel_to_build.php +++ /dev/null @@ -1,17 +0,0 @@ -addColumn("{{build}}", "channel", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->dropColumn("{{build}}", "channel"); - } -} diff --git a/application/console/migrations/m151217_202114_remove_artifact_url_from_job.php b/application/console/migrations/m151217_202114_remove_artifact_url_from_job.php deleted file mode 100644 index e1c716e4..00000000 --- a/application/console/migrations/m151217_202114_remove_artifact_url_from_job.php +++ /dev/null @@ -1,17 +0,0 @@ -dropColumn("{{job}}", "artifact_url_base"); - } - - public function down() - { - $this->addColumn("{{job}}", "artifact_url_base", "varchar(1024) null"); - } -} diff --git a/application/console/migrations/m160104_194709_create_publish_table.php b/application/console/migrations/m160104_194709_create_publish_table.php deleted file mode 100644 index 0a98d193..00000000 --- a/application/console/migrations/m160104_194709_create_publish_table.php +++ /dev/null @@ -1,26 +0,0 @@ -createTable('{{publish}}', [ - 'id' => Schema::TYPE_PK, - 'build_id' => 'int(11) not null', - 'status' => Schema::TYPE_STRING . " null", - - 'created' => 'datetime null', - 'updated' => 'datetime null', - ],"ENGINE=InnoDB DEFAULT CHARSET=utf8"); - $this->addForeignKey('fk_publish_build_id','{{publish}}','build_id', - '{{build}}','id','NO ACTION','NO ACTION'); - } - - public function down() - { - $this->dropTable("{{publish}}"); - } -} diff --git a/application/console/migrations/m160108_213227_rename_publish_to_release.php b/application/console/migrations/m160108_213227_rename_publish_to_release.php deleted file mode 100644 index 6d43ffc1..00000000 --- a/application/console/migrations/m160108_213227_rename_publish_to_release.php +++ /dev/null @@ -1,34 +0,0 @@ -dropForeignKey("fk_publish_build_id", "{{publish}}"); - $this->renameTable("{{publish}}", "{{release}}"); - $this->addColumn("{{release}}", 'result', Schema::TYPE_STRING . " null"); - $this->addColumn("{{release}}", 'error', Schema::TYPE_STRING . " null"); - $this->addColumn("{{release}}", 'channel', Schema::TYPE_STRING . " not null"); - $this->addColumn("{{release}}", 'title', "varchar(30) null"); - $this->addColumn("{{release}}", 'defaultLanguage', Schema::TYPE_STRING . " null"); - - $this->addForeignKey('fk_release_build_id','{{release}}','build_id', - '{{build}}','id','NO ACTION','NO ACTION'); - } - - public function down() - { - $this->dropForeignKey('fk_release_build_id', "{{release}}"); - $this->dropColumn('{{release}}', 'result'); - $this->dropColumn('{{release}}', 'error'); - $this->dropColumn('{{release}}', 'channel'); - $this->dropColumn('{{release}}', 'title'); - $this->dropColumn('{{release}}', 'defaultLanguage'); - $this->renameTable('{{release}}', '{{publish}}'); - $this->addForeignKey('fk_publish_build_id','{{publish}}','build_id', - '{{build}}','id','NO ACTION','NO ACTION'); - } -} diff --git a/application/console/migrations/m160108_213259_remove_channel_from_build.php b/application/console/migrations/m160108_213259_remove_channel_from_build.php deleted file mode 100644 index 8c1b933a..00000000 --- a/application/console/migrations/m160108_213259_remove_channel_from_build.php +++ /dev/null @@ -1,18 +0,0 @@ -dropColumn("{{build}}", "channel"); - } - - public function down() - { - $this->addColumn("{{build}}", "channel", Schema::TYPE_STRING . " null"); - return true; - } -} diff --git a/application/console/migrations/m160112_160804_add_build_number_to_release_table.php b/application/console/migrations/m160112_160804_add_build_number_to_release_table.php deleted file mode 100644 index 75b98877..00000000 --- a/application/console/migrations/m160112_160804_add_build_number_to_release_table.php +++ /dev/null @@ -1,17 +0,0 @@ -addColumn("{{release}}", "build_number", Schema::TYPE_INTEGER . " null"); - } - - public function down() - { - $this->dropColumn("{{release}}", "build_number"); - } -} diff --git a/application/console/migrations/m160112_220005_alter_error_to_hold_url.php b/application/console/migrations/m160112_220005_alter_error_to_hold_url.php deleted file mode 100644 index 7dd452da..00000000 --- a/application/console/migrations/m160112_220005_alter_error_to_hold_url.php +++ /dev/null @@ -1,19 +0,0 @@ -alterColumn("{{build}}", "error", "varchar(2083) null"); - $this->alterColumn("{{release}}", "error", "varchar(2083) null"); - } - - public function down() - { - $this->alterColumn("{{build}}", "error", Schema::TYPE_STRING. " null"); - $this->alterColumn("{{release}}", "error", Schema::TYPE_STRING. " null"); - } -} diff --git a/application/console/migrations/m160119_135843_add_channel_version_number_to_build.php b/application/console/migrations/m160119_135843_add_channel_version_number_to_build.php deleted file mode 100644 index 6ae650e7..00000000 --- a/application/console/migrations/m160119_135843_add_channel_version_number_to_build.php +++ /dev/null @@ -1,19 +0,0 @@ -addColumn("{{build}}", "channel", Schema::TYPE_STRING . " null"); - $this->addColumn("{{build}}", "version_code", Schema::TYPE_INTEGER . " null"); - } - - public function down() - { - $this->dropColumn("{{build}}", "channel"); - $this->dropColumn("{{build}}", "version_code"); - } -} diff --git a/application/console/migrations/m160204_143306_create_email_queue_table.php b/application/console/migrations/m160204_143306_create_email_queue_table.php deleted file mode 100644 index 1002d19a..00000000 --- a/application/console/migrations/m160204_143306_create_email_queue_table.php +++ /dev/null @@ -1,29 +0,0 @@ -createTable('{{email_queue}}',[ - 'id' => 'pk', - 'to' => 'varchar(255) not null', - 'cc' => 'varchar(255) null', - 'bcc' => 'varchar(255) null', - 'subject' => 'varchar(255) not null', - 'text_body' => 'text null', - 'html_body' => 'text null', - 'attempts_count' => 'tinyint(1) null', - 'last_attempt' => 'datetime null', - 'created' => 'datetime null', - 'error' => 'varchar(255) null', - ],"ENGINE=InnoDB DEFAULT CHARSET=utf8"); - - } - - public function down() - { - $this->dropTable('{{email_queue}}'); - }} diff --git a/application/console/migrations/m160303_200821_create_operation_queue_table.php b/application/console/migrations/m160303_200821_create_operation_queue_table.php deleted file mode 100644 index 3fe64096..00000000 --- a/application/console/migrations/m160303_200821_create_operation_queue_table.php +++ /dev/null @@ -1,31 +0,0 @@ -createTable('{{operation_queue}}',[ - 'id' => 'pk', - 'operation' => 'varchar(255) not null', - 'operation_object_id' => 'int(11) null', - 'operation_parms' => 'varchar(2048) null', - 'attempt_count' => 'int not null', - 'last_attempt' => 'datetime null', - 'try_after' => 'datetime null', - 'start_time' => 'datetime null', - 'last_error' => 'varchar(2048) null', - 'created' => 'datetime null', - 'updated' => 'datetime null', - ],"ENGINE=InnoDB DEFAULT CHARSET=utf8"); - $this->createIndex('idx_try_after','{{operation_queue}}','try_after',false); - $this->createIndex('idx_start_time','{{operation_queue}}','start_time',false); - } - - public function down() - { - $this->dropTable('{{operation_queue}}'); - } -} diff --git a/application/console/migrations/m160427_162531_create_client_table.php b/application/console/migrations/m160427_162531_create_client_table.php deleted file mode 100644 index 1ed32002..00000000 --- a/application/console/migrations/m160427_162531_create_client_table.php +++ /dev/null @@ -1,24 +0,0 @@ -createTable('{{client}}',[ - 'id' => 'pk', - 'access_token' => 'varchar(255) not null', - 'prefix' => 'varchar(4) not null', - 'created' => 'datetime null', - 'updated' => 'datetime null', - ],"ENGINE=InnoDB DEFAULT CHARSET=utf8"); - $this->createIndex("idx_accesS_token", "{{client}}", "access_token"); - } - - public function down() - { - $this->dropTable('{{client}}'); - } -} diff --git a/application/console/migrations/m160427_182255_add_client_to_job.php b/application/console/migrations/m160427_182255_add_client_to_job.php deleted file mode 100644 index 434dc7b5..00000000 --- a/application/console/migrations/m160427_182255_add_client_to_job.php +++ /dev/null @@ -1,20 +0,0 @@ -addColumn("{{job}}", "client_id", "int(11) null"); - $this->addForeignKey('fk_job_client_id','{{job}}','client_id', - '{{client}}','id','NO ACTION','NO ACTION'); - } - - public function down() - { - $this->dropForeignKey('fk_job_client_id', '{{job}}'); - $this->dropColumn("{{job}}", "client_id"); - } -} diff --git a/application/console/migrations/m160615_174530_change_artifact_url_of_build.php b/application/console/migrations/m160615_174530_change_artifact_url_of_build.php deleted file mode 100644 index 51c2c6cb..00000000 --- a/application/console/migrations/m160615_174530_change_artifact_url_of_build.php +++ /dev/null @@ -1,20 +0,0 @@ -addColumn("{{build}}", "artifact_url_base", 'varchar(2083) null'); - $this->addColumn("{{build}}", "artifact_files", Schema::TYPE_STRING . " null"); - - $this->dropColumn("{{build}}", "artifact_url"); - } - - public function down() - { - return false; - } -} diff --git a/application/console/migrations/m160630_200721_add_initial_version_code_to_job.php b/application/console/migrations/m160630_200721_add_initial_version_code_to_job.php deleted file mode 100644 index 7083f91a..00000000 --- a/application/console/migrations/m160630_200721_add_initial_version_code_to_job.php +++ /dev/null @@ -1,20 +0,0 @@ -addColumn("{{job}}", "initial_version_code", Schema::TYPE_INTEGER . " DEFAULT 0"); - } - - public function down() - { - $this->dropColumn("{{job}}", "initial_version_code"); - } -} diff --git a/application/console/migrations/m160707_142745_change_initial_to_existing_version_code.php b/application/console/migrations/m160707_142745_change_initial_to_existing_version_code.php deleted file mode 100644 index ef2c8ba1..00000000 --- a/application/console/migrations/m160707_142745_change_initial_to_existing_version_code.php +++ /dev/null @@ -1,17 +0,0 @@ -renameColumn("{{job}}", "initial_version_code", "existing_version_code"); - } - - public function down() - { - $this->renameColumn("{{job}}", "existing_version_code", "initial_version_code"); - } -} diff --git a/application/console/migrations/m160708_134340_add_jenkins_url_to_job.php b/application/console/migrations/m160708_134340_add_jenkins_url_to_job.php deleted file mode 100644 index 803f3ce5..00000000 --- a/application/console/migrations/m160708_134340_add_jenkins_url_to_job.php +++ /dev/null @@ -1,19 +0,0 @@ -addColumn("{{job}}", "jenkins_build_url", "varchar(1024) null"); - $this->addColumn("{{job}}", "jenkins_publish_url", "varchar(1024) null"); - } - - public function down() - { - $this->dropColumn("{{job}}", "jenkins_build_url"); - $this->dropColumn("{{job}}", "jenkins_publish_url"); - } -} diff --git a/application/console/migrations/m160926_142214_add_promote_from_to_release.php b/application/console/migrations/m160926_142214_add_promote_from_to_release.php deleted file mode 100644 index 7f0f2104..00000000 --- a/application/console/migrations/m160926_142214_add_promote_from_to_release.php +++ /dev/null @@ -1,17 +0,0 @@ -addColumn("{{release}}", "promote_from",Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->dropColumn("{{release}}", "promote_from"); - } -} diff --git a/application/console/migrations/m161024_184204_create_project_table.php b/application/console/migrations/m161024_184204_create_project_table.php deleted file mode 100644 index d2cea0b0..00000000 --- a/application/console/migrations/m161024_184204_create_project_table.php +++ /dev/null @@ -1,33 +0,0 @@ -createTable('{{project}}', [ - 'id' => Schema::TYPE_PK, - 'status' => Schema::TYPE_STRING . " null", - 'result' => Schema::TYPE_STRING . " null", - 'error' => Schema::TYPE_STRING . " null", - "url" => "varchar(1024) null", - 'user_id' => Schema::TYPE_STRING . " null", - 'group_id' => Schema::TYPE_STRING . " null", - 'app_id' => Schema::TYPE_STRING . " null", - 'project_name' => Schema::TYPE_STRING . " null", - 'language_code' => Schema::TYPE_STRING . " null", - "publishing_key" => "varchar(1024) null", - - 'created' => 'datetime null', - 'updated' => 'datetime null', - ],"ENGINE=InnoDB DEFAULT CHARSET=utf8"); - } - - public function down() - { - $this->dropTable("{{project}}"); - } - -} diff --git a/application/console/migrations/m161102_191552_add_client_id_to_project.php b/application/console/migrations/m161102_191552_add_client_id_to_project.php deleted file mode 100644 index 2d6a1999..00000000 --- a/application/console/migrations/m161102_191552_add_client_id_to_project.php +++ /dev/null @@ -1,20 +0,0 @@ -addColumn("{{project}}", "client_id", "int(11) null"); - $this->addForeignKey('fk_project_client_id','{{project}}','client_id', - '{{client}}','id','NO ACTION','NO ACTION'); - } - - public function down() - { - $this->dropForeignKey('fk_project_client_id', '{{project}}'); - $this->dropColumn("{{project}}", "client_id"); - } -} diff --git a/application/console/migrations/m161108_135607_alter_project_error_column.php b/application/console/migrations/m161108_135607_alter_project_error_column.php deleted file mode 100644 index ad9089a6..00000000 --- a/application/console/migrations/m161108_135607_alter_project_error_column.php +++ /dev/null @@ -1,18 +0,0 @@ -alterColumn("{{project}}", "error", "varchar(2083) null"); - } - - public function down() - { - $this->alterColumn("{{project}}", "error", Schema::TYPE_STRING. " null"); - } - -} diff --git a/application/console/migrations/m180613_134204_change_build_number_to_build_guid.php b/application/console/migrations/m180613_134204_change_build_number_to_build_guid.php deleted file mode 100644 index c3bc2942..00000000 --- a/application/console/migrations/m180613_134204_change_build_number_to_build_guid.php +++ /dev/null @@ -1,22 +0,0 @@ -dropColumn("{{build}}", "build_number"); - $this->addColumn("{{build}}", "build_guid", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->addColumn("{{build}}", "build_number", Schema::TYPE_INTEGER . " null"); - $this->dropColumn("{{build}}", "build_guid"); - } -} diff --git a/application/console/migrations/m180625_151653_change_build_number_to_build_guid_for_release.php b/application/console/migrations/m180625_151653_change_build_number_to_build_guid_for_release.php deleted file mode 100644 index 2d5cd68f..00000000 --- a/application/console/migrations/m180625_151653_change_build_number_to_build_guid_for_release.php +++ /dev/null @@ -1,22 +0,0 @@ -dropColumn("{{release}}", "build_number"); - $this->addColumn("{{release}}", "build_guid", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->addColumn("{{release}}", "build_number", Schema::TYPE_INTEGER . " null"); - $this->dropColumn("{{release}}", "build_guid"); - } -} diff --git a/application/console/migrations/m180629_183318_addConsoleTextUrl.php b/application/console/migrations/m180629_183318_addConsoleTextUrl.php deleted file mode 100644 index 328deefc..00000000 --- a/application/console/migrations/m180629_183318_addConsoleTextUrl.php +++ /dev/null @@ -1,23 +0,0 @@ -addColumn("{{build}}", "console_text_url", Schema::TYPE_STRING . " null"); - $this->addColumn("{{release}}", "console_text_url", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->dropColumn("{{build}}", "console_text_url"); - $this->dropColumn("{{release}}", "console_text_url"); - } - -} diff --git a/application/console/migrations/m180704_191855_addCodeBuildUrl.php b/application/console/migrations/m180704_191855_addCodeBuildUrl.php deleted file mode 100644 index 974c209a..00000000 --- a/application/console/migrations/m180704_191855_addCodeBuildUrl.php +++ /dev/null @@ -1,23 +0,0 @@ -addColumn("{{build}}", "codebuild_url", Schema::TYPE_STRING . " null"); - $this->addColumn("{{release}}", "codebuild_url", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->dropColumn("{{build}}", "codebuild_url"); - $this->dropColumn("{{release}}", "codebuild_url"); - } - -} diff --git a/application/console/migrations/m190308_210456_add_target_env_to_build.php b/application/console/migrations/m190308_210456_add_target_env_to_build.php deleted file mode 100644 index 5b2ac1cc..00000000 --- a/application/console/migrations/m190308_210456_add_target_env_to_build.php +++ /dev/null @@ -1,23 +0,0 @@ -addColumn("{{build}}", "targets", Schema::TYPE_STRING . " null"); - $this->addColumn("{{build}}", "environment", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->dropColumn("{{build}}", "targets"); - $this->dropColumn("{{build}}", "environment"); - } - -} diff --git a/application/console/migrations/m190311_200559_add_target_env_to_release.php b/application/console/migrations/m190311_200559_add_target_env_to_release.php deleted file mode 100644 index 09c4335a..00000000 --- a/application/console/migrations/m190311_200559_add_target_env_to_release.php +++ /dev/null @@ -1,22 +0,0 @@ -addColumn("{{release}}", "targets", Schema::TYPE_STRING . " null"); - $this->addColumn("{{release}}", "environment", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->dropColumn("{{release}}", "targets"); - $this->dropColumn("{{release}}", "environment"); - } -} diff --git a/application/console/migrations/m190424_142509_add_artifacts_to_release.php b/application/console/migrations/m190424_142509_add_artifacts_to_release.php deleted file mode 100644 index b8847543..00000000 --- a/application/console/migrations/m190424_142509_add_artifacts_to_release.php +++ /dev/null @@ -1,24 +0,0 @@ -addColumn("{{release}}", "artifact_url_base", Schema::TYPE_STRING . " null"); - $this->addColumn("{{release}}", "artifact_files", Schema::TYPE_STRING . " null"); - } - - public function down() - { - $this->dropColumn("{{release}}", "artifact_url_base"); - $this->dropColumn("{{release}}", "artifact_files"); - return false; - } -} diff --git a/application/console/migrations/m200128_220041_alter_build_environment.php b/application/console/migrations/m200128_220041_alter_build_environment.php deleted file mode 100644 index 213afe05..00000000 --- a/application/console/migrations/m200128_220041_alter_build_environment.php +++ /dev/null @@ -1,20 +0,0 @@ -alterColumn("{{build}}", "environment", Schema::TYPE_TEXT. " null"); - } - public function down() - { - $this->alterColumn("{{build}}", "environment", "varchar(255) null"); - } -} - diff --git a/application/console/migrations/m200616_193900_alter_multiple_apks.php b/application/console/migrations/m200616_193900_alter_multiple_apks.php deleted file mode 100644 index 5b960141..00000000 --- a/application/console/migrations/m200616_193900_alter_multiple_apks.php +++ /dev/null @@ -1,19 +0,0 @@ -alterColumn("{{build}}", "artifact_files", "varchar(4096) null"); - } - - public function down() - { - $this->alterColumn("{{build}}", "artifact_files", Schema::TYPE_STRING. " null"); - } -} diff --git a/application/console/migrations/m210825_131342_alter_release_environment.php b/application/console/migrations/m210825_131342_alter_release_environment.php deleted file mode 100644 index 0e21c3e4..00000000 --- a/application/console/migrations/m210825_131342_alter_release_environment.php +++ /dev/null @@ -1,20 +0,0 @@ -alterColumn("{{release}}", "environment", Schema::TYPE_TEXT. " null"); - } - - public function down() - { - $this->alterColumn("{{release}}", "environment", "varchar(255) null"); - } -} diff --git a/application/console/models/.gitkeep b/application/console/models/.gitkeep deleted file mode 100644 index 72e8ffc0..00000000 --- a/application/console/models/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/application/console/runtime/.gitignore b/application/console/runtime/.gitignore deleted file mode 100644 index c96a04f0..00000000 --- a/application/console/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/application/console/views/cron/scripts/appbuilders_build.php b/application/console/views/cron/scripts/appbuilders_build.php deleted file mode 100644 index ca5bc7ee..00000000 --- a/application/console/views/cron/scripts/appbuilders_build.php +++ /dev/null @@ -1,72 +0,0 @@ - -version: 0.2 - -env: - variables: - "PUBLISHER" : "wycliffeusa" - "BUILD_NUMBER" : "0" - "VERSION_CODE" : "42" - "APP_BUILDER_SCRIPT_PATH" : "scripture-app-builder" - "SECRETS_BUCKET": "sil-prd-aps-secrets" - "SECRETS_DIR" : "/secrets" - -phases: - install: - commands: - pre_build: - commands: - - OUTPUT_DIR="/${BUILD_NUMBER}" - - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build/google_play_store/${PUBLISHER}" - - mkdir "${SECRETS_DIR}" - - mkdir "${OUTPUT_DIR}" - - aws s3 sync "${SECRETS_S3}" "${SECRETS_DIR}" - - export KSP=$(cat "${SECRETS_DIR}/ksp.txt") - - export KA=$(cat "${SECRETS_DIR}/ka.txt") - - export KAP=$(cat "${SECRETS_DIR}/kap.txt") - - export GRADLE_OPTS="-Dorg.gradle.daemon=false" - build: - commands: - - KS="${SECRETS_DIR}/${PUBLISHER}.keystore" - - echo "BUILD_NUMBER=${BUILD_NUMBER}" - - echo "VERSION_CODE=${VERSION_CODE}" - - OUTPUT_DIR="/${BUILD_NUMBER}" - - PROJNAME=$(basename *.appDef .appDef) - - mv "${PROJNAME}.appDef" build.appDef - - mv "${PROJNAME}_data" build_data - - APPDEF_VERSION=$(grep "version code=" build.appDef|awk -F"\"" '{print $2}') - - echo "APPDEF_VERSION=${APPDEF_VERSION}" - - if [ "$APPDEF_VERSION" -ge "$VERSION_CODE" ]; then VERSION_CODE=$((APPDEF_VERSION+1)); fi - - VERSION_NAME=$(dpkg -s scripture-app-builder | grep 'Version' | awk -F '[ +]' '{print $2}') - - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -ks $KS -ksp $KSP -ka $KA -kap $KAP -fp apk.output=$OUTPUT_DIR -vc $VERSION_CODE -vn $VERSION_NAME -ft share-app-link=true - - echo $(awk -F '[<>]' '/package/{print $3}' build.appDef) > $OUTPUT_DIR/package_name.txt - - echo $VERSION_CODE > $OUTPUT_DIR/version_code.txt - - "echo \"{ \\\"version\\\" : \\\"${VERSION_NAME}.${VERSION_CODE}\\\", \\\"versionName\\\" : \\\"${VERSION_NAME}\\\", \\\"versionCode\\\" : \\\"${VERSION_CODE}\\\" } \" > $OUTPUT_DIR/version.json" - - if [ -f "build_data/about/about.txt" ]; then cp build_data/about/about.txt $OUTPUT_DIR/; fi - - PUBLISH_DIR="build_data/publish" - - PLAY_LISTING_DIR="${PUBLISH_DIR}/play-listing" - - LIST_DIR="${PLAY_LISTING_DIR}/" - - MANIFEST_FILE="manifest.txt" - - if [ -f $LIST_DIR$MANIFEST_FILE ]; then rm $LIST_DIR$MANIFEST_FILE; fi; - - FILE_LIST=$(find $PLAY_LISTING_DIR -type f -print) - - for f in $FILE_LIST; do fn=${f#*"$PLAY_LISTING_DIR/"}; echo $fn >> $OUTPUT_DIR/$MANIFEST_FILE; done - - if [ -d "$PLAY_LISTING_DIR" ]; then cp -r "$PLAY_LISTING_DIR" $OUTPUT_DIR; find $OUTPUT_DIR -name whats_new.txt | while read filename; do DIR=$(dirname "${filename}"); cp "$filename" $OUTPUT_DIR; mkdir "${DIR}/changelogs"; mv "$filename" "${DIR}/changelogs/${VERSION_CODE}.txt"; done; fi - - mv build_data "${PROJNAME}_data" - - mv build.appDef "${PROJNAME}.appDef" - #- if [ "$CODEBUILD_BUILD_SUCCEEDING" -gt "0" ]; then git remote -v; git tag $VERSION_CODE; git push origin $VERSION_CODE; fi - #post_build: - #commands: - -artifacts: - files: - - $OUTPUT_DIR/**/* - # - location - #discard-paths: yes - #base-directory: location -#cache: - #paths: - # - paths \ No newline at end of file diff --git a/application/console/views/cron/scripts/appbuilders_build.yml b/application/console/views/cron/scripts/appbuilders_build.yml deleted file mode 100644 index d8070e8f..00000000 --- a/application/console/views/cron/scripts/appbuilders_build.yml +++ /dev/null @@ -1,66 +0,0 @@ -version: 0.2 - -env: - variables: - "PUBLISHER" : "wycliffeusa" - "BUILD_NUMBER" : "0" - "VERSION_CODE" : "42" - "APP_BUILDER_SCRIPT_PATH" : "scripture-app-builder" - "SECRETS_BUCKET": "sil-prd-aps-secrets" - "SECRETS_DIR" : "/secrets" - -phases: - install: - commands: - pre_build: - commands: - - OUTPUT_DIR="/${BUILD_NUMBER}" - - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build/google_play_store/${PUBLISHER}" - - mkdir "${SECRETS_DIR}" - - mkdir "${OUTPUT_DIR}" - - aws s3 sync "${SECRETS_S3}" "${SECRETS_DIR}" - - export KSP=$(cat "${SECRETS_DIR}/ksp.txt") - - export KA=$(cat "${SECRETS_DIR}/ka.txt") - - export KAP=$(cat "${SECRETS_DIR}/kap.txt") - - export GRADLE_OPTS="-Dorg.gradle.daemon=false" - build: - commands: - - KS="${SECRETS_DIR}/${PUBLISHER}.keystore" - - echo "BUILD_NUMBER=${BUILD_NUMBER}" - - echo "VERSION_CODE=${VERSION_CODE}" - - OUTPUT_DIR="/${BUILD_NUMBER}" - - PROJNAME=$(basename *.appDef .appDef) - - mv "${PROJNAME}.appDef" build.appDef - - mv "${PROJNAME}_data" build_data - - APPDEF_VERSION=$(grep "version code=" build.appDef|awk -F"\"" '{print $2}') - - echo "APPDEF_VERSION=${APPDEF_VERSION}" - - if [ "$APPDEF_VERSION" -ge "$VERSION_CODE" ]; then VERSION_CODE=$((APPDEF_VERSION+1)); fi - - VERSION_NAME=$(dpkg -s scripture-app-builder | grep 'Version' | awk -F '[ +]' '{print $2}') - - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -ks $KS -ksp $KSP -ka $KA -kap $KAP -fp apk.output=$OUTPUT_DIR -vc $VERSION_CODE -vn $VERSION_NAME -ft share-app-link=true - - echo $(awk -F '[<>]' '/package/{print $3}' build.appDef) > $OUTPUT_DIR/package_name.txt - - echo $VERSION_CODE > $OUTPUT_DIR/version_code.txt - - "echo \"{ \\\"version\\\" : \\\"${VERSION_NAME}.${VERSION_CODE}\\\", \\\"versionName\\\" : \\\"${VERSION_NAME}\\\", \\\"versionCode\\\" : \\\"${VERSION_CODE}\\\" } \" > $OUTPUT_DIR/version.json" - - if [ -f "build_data/about/about.txt" ]; then cp build_data/about/about.txt $OUTPUT_DIR/; fi - - PUBLISH_DIR="build_data/publish" - - PLAY_LISTING_DIR="${PUBLISH_DIR}/play-listing" - - LIST_DIR="${PLAY_LISTING_DIR}/" - - MANIFEST_FILE="manifest.txt" - - if [ -f $LIST_DIR$MANIFEST_FILE ]; then rm $LIST_DIR$MANIFEST_FILE; fi; - - FILE_LIST=$(find $PLAY_LISTING_DIR -type f -print) - - for f in $FILE_LIST; do fn=${f#*"$PLAY_LISTING_DIR/"}; echo $fn >> $OUTPUT_DIR/$MANIFEST_FILE; done - - if [ -d "$PLAY_LISTING_DIR" ]; then cp -r "$PLAY_LISTING_DIR" $OUTPUT_DIR; find $OUTPUT_DIR -name whats_new.txt | while read filename; do DIR=$(dirname "${filename}"); cp "$filename" $OUTPUT_DIR; mkdir "${DIR}/changelogs"; mv "$filename" "${DIR}/changelogs/${VERSION_CODE}.txt"; done; fi - - mv build_data "${PROJNAME}_data" - - mv build.appDef "${PROJNAME}.appDef" - #- if [ "$CODEBUILD_BUILD_SUCCEEDING" -gt "0" ]; then git remote -v; git tag $VERSION_CODE; git push origin $VERSION_CODE; fi - #post_build: - #commands: - -artifacts: - files: - - $OUTPUT_DIR/**/* - # - location - #discard-paths: yes - #base-directory: location -#cache: - #paths: - # - paths \ No newline at end of file diff --git a/application/console/views/cron/scripts/appbuilders_publish.php b/application/console/views/cron/scripts/appbuilders_publish.php deleted file mode 100644 index 001d1f69..00000000 --- a/application/console/views/cron/scripts/appbuilders_publish.php +++ /dev/null @@ -1,52 +0,0 @@ - -version: 0.2 - -env: - variables: - "PUBLISHER" : "wycliffeusa" - "SECRETS_BUCKET": "sil-prd-aps-secrets" - "CHANNEL" : "alpha" - "PROMOTE_FROM" : "" - "ARTIFACTS_S3_DIR" : "s3://dem-aps-artifacts/dem/jobs/build_scriptureappbuilder_1/3" - "SECRETS_DIR" : "/secrets" - "ARTIFACTS_DIR" : "/artifacts" - "SCRIPT_DIR" : "/script" - "SCRIPT_S3" : "s3://s3url/default" - "TARGETS" : "" - "RELEASE_NUMBER" : "0" - -phases: - install: - commands: - pre_build: - commands: - - OUTPUT_DIR="/${RELEASE_NUMBER}" - - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/publish" - - mkdir "${SECRETS_DIR}" - - mkdir "${ARTIFACTS_DIR}" - - mkdir "${SCRIPT_DIR}" - - mkdir "${OUTPUT_DIR}" - - echo "${SCRIPT_S3}" - - aws s3 sync "${ARTIFACTS_S3_DIR}" "${ARTIFACTS_DIR}" - - aws s3 sync "${SCRIPT_S3}" "${SCRIPT_DIR}" - - ls -l "${ARTIFACTS_DIR}" - build: - commands: - - TARGETS="${TARGETS}" bash ${SCRIPT_DIR}/publish.sh - #post_build: - #commands: - -artifacts: - files: - - $OUTPUT_DIR/**/* - # - location - #discard-paths: yes - #base-directory: location -#cache: - #paths: - # - paths \ No newline at end of file diff --git a/application/console/views/cron/scripts/appbuilders_publish.yml b/application/console/views/cron/scripts/appbuilders_publish.yml deleted file mode 100644 index f9e258d7..00000000 --- a/application/console/views/cron/scripts/appbuilders_publish.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: 0.2 - -env: - variables: - "PUBLISHER" : "wycliffeusa" - "SECRETS_BUCKET": "sil-prd-aps-secrets" - "CHANNEL" : "alpha" - "PROMOTE_FROM" : "" - "ARTIFACTS_S3_DIR" : "s3://dem-aps-artifacts/dem/jobs/build_scriptureappbuilder_1/3" - "SECRETS_DIR" : "/secrets" - "ARTIFACTS_DIR" : "/artifacts" - "SCRIPT_DIR" : "/script" - "SCRIPT_S3" : "s3://s3url/default" - "TARGETS" : "" - "RELEASE_NUMBER" : "0" - -phases: - install: - commands: - pre_build: - commands: - - OUTPUT_DIR="/${RELEASE_NUMBER}" - - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/publish" - - mkdir "${SECRETS_DIR}" - - mkdir "${ARTIFACTS_DIR}" - - mkdir "${SCRIPT_DIR}" - - mkdir "${OUTPUT_DIR}" - - echo "${SCRIPT_S3}" - - aws s3 sync "${ARTIFACTS_S3_DIR}" "${ARTIFACTS_DIR}" - - aws s3 sync "${SCRIPT_S3}" "${SCRIPT_DIR}" - - ls -l "${ARTIFACTS_DIR}" - build: - commands: - - TARGETS="${TARGETS}" bash ${SCRIPT_DIR}/publish.sh - #post_build: - #commands: - -artifacts: - files: - - $OUTPUT_DIR/**/* - # - location - #discard-paths: yes - #base-directory: location -#cache: - #paths: - # - paths \ No newline at end of file diff --git a/application/console/views/cron/scripts/appbuilders_s3_build.php b/application/console/views/cron/scripts/appbuilders_s3_build.php deleted file mode 100644 index af776a60..00000000 --- a/application/console/views/cron/scripts/appbuilders_s3_build.php +++ /dev/null @@ -1,52 +0,0 @@ - -version: 0.2 - -env: - variables: - "PUBLISHER" : "wycliffeusa" - "BUILD_NUMBER" : "0" - "VERSION_CODE" : "42" - "APP_BUILDER_SCRIPT_PATH" : "scripture-app-builder" - "SECRETS_BUCKET": "sil-prd-aps-secrets" - "SECRETS_DIR" : "/secrets" - "PROJECT_DIR" : "/project" - "SCRIPT_DIR" : "/script" - "PROJECT_S3" : "s3://s3url" - "SCRIPT_S3" : "s3://s3url/default" - "TARGETS" : "" - -phases: - install: - commands: - pre_build: - commands: - - OUTPUT_DIR="/${BUILD_NUMBER}" - - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build" - - mkdir "${SECRETS_DIR}" - - mkdir "${OUTPUT_DIR}" - - mkdir "${PROJECT_DIR}" - - mkdir "${SCRIPT_DIR}" - - echo "PROJECT_S3=${PROJECT_S3}" - - aws s3 sync "${PROJECT_S3}" "${PROJECT_DIR}" - - aws s3 sync "${SCRIPT_S3}" "${SCRIPT_DIR}" - - export GRADLE_OPTS="-Dorg.gradle.daemon=false" - build: - commands: - - TARGETS="${TARGETS}" bash ${SCRIPT_DIR}/build.sh - #post_build: - #commands: - -artifacts: - files: - - $OUTPUT_DIR/**/* - # - location - #discard-paths: yes - #base-directory: location -#cache: - #paths: - # - paths \ No newline at end of file diff --git a/application/console/views/cron/scripts/appbuilders_s3_build.yml b/application/console/views/cron/scripts/appbuilders_s3_build.yml deleted file mode 100644 index c534b36e..00000000 --- a/application/console/views/cron/scripts/appbuilders_s3_build.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: 0.2 - -env: - variables: - "PUBLISHER" : "wycliffeusa" - "BUILD_NUMBER" : "0" - "VERSION_CODE" : "42" - "APP_BUILDER_SCRIPT_PATH" : "scripture-app-builder" - "SECRETS_BUCKET": "sil-prd-aps-secrets" - "SECRETS_DIR" : "/secrets" - "PROJECT_DIR" : "/project" - "SCRIPT_DIR" : "/script" - "PROJECT_S3" : "s3://s3url" - "SCRIPT_S3" : "s3://s3url/default" - "TARGETS" : "" - -phases: - install: - commands: - pre_build: - commands: - - OUTPUT_DIR="/${BUILD_NUMBER}" - - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build" - - mkdir "${SECRETS_DIR}" - - mkdir "${OUTPUT_DIR}" - - mkdir "${PROJECT_DIR}" - - mkdir "${SCRIPT_DIR}" - - echo "PROJECT_S3=${PROJECT_S3}" - - aws s3 sync "${PROJECT_S3}" "${PROJECT_DIR}" - - aws s3 sync "${SCRIPT_S3}" "${SCRIPT_DIR}" - - export GRADLE_OPTS="-Dorg.gradle.daemon=false" - build: - commands: - - TARGETS="${TARGETS}" bash ${SCRIPT_DIR}/build.sh - #post_build: - #commands: - -artifacts: - files: - - $OUTPUT_DIR/**/* - # - location - #discard-paths: yes - #base-directory: location -#cache: - #paths: - # - paths \ No newline at end of file diff --git a/application/console/views/cron/scripts/project_default/default/default.zip b/application/console/views/cron/scripts/project_default/default/default.zip deleted file mode 100644 index 9b87ecbca2c038438ff81d77220d1ced2832c3b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 284 zcmWIWW@h1H0D(zid46C9l;B_xU`R<#ODxSP(GQK_VK|-B6|*_3E9M8I2txo)UBXDZ zpt|)+DoQ{GfHWfvU}O?y#$_=N%pDGI9YIWlGdSSR2=K;jf-nQZge8q;NG6~-i39GW T0B=?{kVQ;D*bk)3K^z7Ebd)&= diff --git a/application/console/views/cron/scripts/scriptureappbuilder/google.groovy b/application/console/views/cron/scripts/scriptureappbuilder/google.groovy deleted file mode 100644 index 104ea21b..00000000 --- a/application/console/views/cron/scripts/scriptureappbuilder/google.groovy +++ /dev/null @@ -1,17 +0,0 @@ - -package scriptureappbuilder; - -class google { - static Closure credentialsBindingWrapper(publisherName) { - return { project -> - project / 'buildWrappers' << 'org.jenkinsci.plugins.credentialsbinding.impl.SecretBuildWrapper' { - bindings { - 'org.jenkinsci.plugins.credentialsbinding.impl.FileBinding' { - variable 'PAJ' - credentialsId "${publisherName}-playstore-api-json" - } - } - } - } - } -} \ No newline at end of file diff --git a/application/console/views/cron/scripts/scriptureappbuilder/jobs.groovy b/application/console/views/cron/scripts/scriptureappbuilder/jobs.groovy deleted file mode 100644 index e8b32855..00000000 --- a/application/console/views/cron/scripts/scriptureappbuilder/jobs.groovy +++ /dev/null @@ -1,155 +0,0 @@ -package scriptureappbuilder; -import scriptureappbuilder.keystore; -import scriptureappbuilder.google; - -class jobs { - static gitBranch = '*/master' - static buildJobScript = ''' -PROJNAME=$(basename *.appDef .appDef) -mv "${PROJNAME}.appDef" build.appDef -mv "${PROJNAME}_data" build_data -APPDEF_VERSION=$(grep "version code=" build.appDef|awk -F"\\"" '{print $2}') -if [ "$APPDEF_VERSION" -ge "$VERSION_CODE" ]; then - VERSION_CODE=$((APPDEF_VERSION+1)) -fi -VERSION_NUMBER=$(dpkg -s scripture-app-builder | grep 'Version' | awk -F '[ +]' '{print $2}') -{ set +x; } 2>/dev/null -/usr/share/scripture-app-builder/sab.sh -load build.appDef -no-save -build -ta 22 -ks $KS -ksp $KSP -ka $KA -kap $KAP -fp apk.output=$WORKSPACE/output -vc $VERSION_CODE -vn $VERSION_NUMBER -ft share-app-link=true -set -x -echo $(awk -F '[<>]' '/package/{print $3}' build.appDef) > output/package_name.txt -echo $VERSION_CODE > output/version_code.txt -if [ -f "build_data/about/about.txt" ]; then - cp build_data/about/about.txt output/ -fi -PUBLISH_DIR="build_data/publish/play-listing" -if [ -d "$PUBLISH_DIR" ]; then - cp -r "$PUBLISH_DIR" output - find output -name whats_new.txt | while read filename; do - DIR=$(dirname "${filename}") - mkdir "${DIR}/changelogs" - mv "$filename" "${DIR}/changelogs/${VERSION_CODE}.txt" - done -fi -mv build_data "${PROJNAME}_data" -mv build.appDef "${PROJNAME}.appDef" - -# Work-around for https://issues.jenkins-ci.org/browse/JENKINS-35102 -killall Xvfb -''' - static artifactFiles = 'output/**' - - static void codecommitBuildJob(jobContext, gitUrl, publisherName) { - jobContext.with { - description "Create App for ${gitUrl}" - - wrappers { - timestamps() - xvfb('default') { - screen('1024x768x24') - autoDisplayName(true) - } - - timeout { - noActivity(180) - failBuild() - writeDescription('Build failed due to timeout: {0} seconds') - } - } - - properties { - zenTimestamp('yyyy_MM_dd_HH_mm_Z') - } - - configure keystore."credentialsBindingWrapper"(publisherName) - - label('android-sdk && app-builders') - - scm { - git { - remote { - url(gitUrl) - credentials('appbuilder-buildagent') - } - branch(gitBranch) - configure { node -> - node / 'extensions' / 'hudson.plugins.git.extensions.impl.UserIdentity' << { - delegate.name('AppBuilder_BuildAgent') - email('appbuilder_buildagent@sil.org') - } - } - extensions { - cleanBeforeCheckout() - } - } - } - - parameters { - stringParam('VERSION_CODE', '', '' ) - } - - steps { - shell(buildJobScript) - } - - publishers { - archiveArtifacts(artifactFiles) - git { - pushMerge(true) - pushOnlyIfSuccess(true) - forcePush(false) - branch('origin', 'master') - tag('origin', '$BUILD_TAG-$VERSION_CODE-$BUILD_TIMESTAMP') { - create(true) - } - } - } - - logRotator { - numToKeep(5) - artifactNumToKeep(2) - } - } - } - - static publishJobScript = ''' -rm -rf * -wget -r -i "${ARTIFACT_URL}play-listing.html" -PLAY_LISTING_DIR=$(find -name play-listing -print) -cd $PLAY_LISTING_DIR -cd .. -wget "$APK_URL" -wget "${ARTIFACT_URL}package_name.txt" -wget "${ARTIFACT_URL}version_code.txt" - -set +x -if [ -z "$PROMOTE_FROM" ]; then - fastlane supply -j $PAJ -b *.apk -p $(cat package_name.txt) --track $CHANNEL -m play-listing -else - fastlane supply -j $PAJ -b *.apk -p $(cat package_name.txt) --track $PROMOTE_FROM --track_promote_to $CHANNEL -m play-listing -fi -''' - static void googleplayPublishJob(jobContext, gitUrl, publisherName, buildJobName) { - jobContext.with { - description "Publish App for ${gitUrl}" - - configure google."credentialsBindingWrapper"(publisherName) - - label('fastlane-supply') - parameters { - choiceParam('CHANNEL', ['production', 'alpha', 'beta']) - stringParam('ARTIFACT_URL', '', '' ) - stringParam('APK_URL', '', '' ) - stringParam('PUBLIC_URL', '', '') - stringParam('PROMOTE_FROM', '', '') - } - steps { - shell(publishJobScript) - } - - logRotator { - numToKeep(5) - } - } - } -} - diff --git a/application/console/views/cron/scripts/scriptureappbuilder/keystore.groovy b/application/console/views/cron/scripts/scriptureappbuilder/keystore.groovy deleted file mode 100644 index 9a00babb..00000000 --- a/application/console/views/cron/scripts/scriptureappbuilder/keystore.groovy +++ /dev/null @@ -1,30 +0,0 @@ -package scriptureappbuilder; - -class keystore { - - static Closure credentialsBindingWrapper(publisherName) { - return { project -> - project / 'buildWrappers' << 'org.jenkinsci.plugins.credentialsbinding.impl.SecretBuildWrapper' { - bindings { - 'org.jenkinsci.plugins.credentialsbinding.impl.FileBinding' { - variable 'KS' - credentialsId "${publisherName}-ks" - } - 'org.jenkinsci.plugins.credentialsbinding.impl.StringBinding' { - variable 'KSP' - credentialsId "${publisherName}-ksp" - } - 'org.jenkinsci.plugins.credentialsbinding.impl.StringBinding' { - - variable 'KA' - credentialsId "${publisherName}-ka" - } - 'org.jenkinsci.plugins.credentialsbinding.impl.StringBinding' { - variable 'KAP' - credentialsId "${publisherName}-kap" - } - } - } - } - } -} diff --git a/application/console/views/cron/scripts/scriptureappbuilder_build.php b/application/console/views/cron/scripts/scriptureappbuilder_build.php deleted file mode 100644 index 0969aae0..00000000 --- a/application/console/views/cron/scripts/scriptureappbuilder_build.php +++ /dev/null @@ -1,14 +0,0 @@ - -import scriptureappbuilder.jobs -def buildJobName = '' -def publishJobName = '' -def gitUrl = '' -def publisherName = '' -job(buildJobName) { - jobs.codecommitBuildJob(delegate, gitUrl, publisherName) -} diff --git a/application/console/views/cron/scripts/scriptureappbuilder_publish.php b/application/console/views/cron/scripts/scriptureappbuilder_publish.php deleted file mode 100644 index 383e5b56..00000000 --- a/application/console/views/cron/scripts/scriptureappbuilder_publish.php +++ /dev/null @@ -1,14 +0,0 @@ - -import scriptureappbuilder.jobs -def buildJobName = '' -def publishJobName = '' -def gitUrl = '' -def publisherName = '' -job(publishJobName) { - jobs.googleplayPublishJob(delegate, gitUrl, publisherName, buildJobName) -} diff --git a/application/console/views/cron/scripts/upload/default/build.sh b/application/console/views/cron/scripts/upload/default/build.sh deleted file mode 100644 index 1f1ab435..00000000 --- a/application/console/views/cron/scripts/upload/default/build.sh +++ /dev/null @@ -1,711 +0,0 @@ -#!/usr/bin/env bash -set -e -o pipefail -#set -x - -LOG_FILE="${OUTPUT_DIR}"/console.log -exec > >(tee "${LOG_FILE}") 2>&1 - -export PATH="$HOME/.rbenv/bin:$PATH" -eval "$(rbenv init -)" - -BUILD_DIR=/tmp/build -mkdir -p "$BUILD_DIR" -SCRIPT_OPT="-fp build=${BUILD_DIR}" - -sync_build_secrets() { - SECRETS_SUBDIR=$1 - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/build" - echo "sync build secrets" - echo "SECRETS_SUBDIR = ${SECRETS_SUBDIR}" - aws s3 sync "${SECRETS_S3}/${SECRETS_SUBDIR}" "${SECRETS_DIR}" -} - -sync_publish_secrets() { - SECRETS_SUBDIR=$1 - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/publish" - echo "sync publish secrets" - echo "SECRETS_SUBDIR = ${SECRETS_SUBDIR}" - aws s3 sync "${SECRETS_S3}/${SECRETS_SUBDIR}" "${SECRETS_DIR}" -} - -check_audio_sources() { - if [[ "${BUILD_AUDIO_UPDATE}" == "1" ]]; then - if [[ "${AUDIO_UPDATE_SOURCE}" != "" ]]; then - ADD_AUDIO_UPDATE_SOURCE=1 - SOURCES="$(xmllint --xpath "/app-definition/audio-sources/audio-source/name" "${PROJECT_DIR}/build.appDef")" - IFS='=' read -ra UPDATE_SOURCES <<< "$AUDIO_UPDATE_SOURCE" - for i in "${UPDATE_SOURCES[@]}"; do - if [[ "$SOURCES" != *"${i}"* ]]; then - ADD_AUDIO_UPDATE_SOURCE=0 - fi - done - fi - fi -} - -replace_audio_sources() { - SRC_UPDATE_SOURCE="${UPDATE_SOURCES[0]}" - DST_UPDATE_SOURCE="${UPDATE_SOURCES[1]}" - xmlstarlet ed -u "/app-definition/audio-sources/audio-source/name[text() = '${SRC_UPDATE_SOURCE}']" -v "SCRIPTORIA_SRC_SOURCE" "${PROJECT_DIR}/build.appDef" > "${PROJECT_DIR}/tmp.appDef" - xmlstarlet ed -u "/app-definition/audio-sources/audio-source/name[text() = '${DST_UPDATE_SOURCE}']" -v "SCRIPTORIA_DST_SOURCE" "${PROJECT_DIR}/tmp.appDef" > "${PROJECT_DIR}/build.appDef" - if [[ "${AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE}" == "${SRC_UPDATE_SOURCE}" ]]; then - export AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE="SCRIPTORIA_SRC_SOURCE" - fi - rm "${PROJECT_DIR}/tmp.appDef" -} - -process_audio_sources() { - check_audio_sources - if [[ "${ADD_AUDIO_UPDATE_SOURCE}" == "1" ]]; then - replace_audio_sources - SCRIPT_OPT="${SCRIPT_OPT} -audio-update-source SCRIPTORIA_SRC_SOURCE=SCRIPTORIA_DST_SOURCE" - fi -} - -process_audio_download() { - if [[ "${BUILD_AUDIO_DOWNLOAD}" == "1" ]]; then - if [[ "${AUDIO_DOWNLOAD_MISSING_ASSETS_KEY}" != "" ]]; then - if [[ "${BUILD_AUDIO_DOWNLOAD_URL}" == "" ]]; then - BUILD_AUDIO_DOWNLOAD_URL="https://4.dbt.io" - fi - SCRIPT_OPT="${SCRIPT_OPT} -audio-download-missing-assets-key ${AUDIO_DOWNLOAD_MISSING_ASSETS_KEY} -audio-download-url ${BUILD_AUDIO_DOWNLOAD_URL}" - if [[ "${AUDIO_DOWNLOAD_BITRATE}" != "" ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -audio-download-bitrate ${AUDIO_DOWNLOAD_BITRATE}" - fi - elif [[ "${AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE}" != "" ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -audio-download-missing-assets-source ${AUDIO_DOWNLOAD_MISSING_ASSETS_SOURCE}" - fi - if [[ "${AUDIO_DOWNLOAD_CODEC}" != "" ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -audio-download-codec ${AUDIO_DOWNLOAD_CODEC}" - fi - fi -} - -build_apk() { - echo "Build APK" - cd "$PROJECT_DIR" || exit 1 - if [[ "${BUILD_MANAGE_VERSION_CODE}" != "0" ]]; then - VERSION_CODE=$((VERSION_CODE + 1)) - fi - - if [[ "${BUILD_COMPUTE_TYPE}" != "" ]]; then - if [[ "${BUILD_COMPUTE_TYPE}" != "small" ]]; then - export _JAVA_OPTIONS="-Xmx3072M" - fi - fi - if [[ "${BUILD_JAVA_MAX_HEAP_SIZE}" == "1" ]]; then - export _JAVA_OPTIONS="-Xmx2048M" - fi - - echo "BUILD_SHARE_APP_LINK=${BUILD_SHARE_APP_LINK}" - if [[ "${BUILD_SHARE_APP_LINK}" != "0" ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -ft share-app-link=true" - fi - echo "BUILD_SHARE_APP_INSTALLER=${BUILD_SHARE_APP_INSTALLER}" - if [[ "${BUILD_SHARE_APP_INSTALLER}" == "1" ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -ft share-apk-file=true" - fi - echo "BUILD_SHARE_DOWNLOAD_APP_LINK=${BUILD_SHARE_DOWNLOAD_APP_LINK}" - if [[ "${BUILD_SHARE_DOWNLOAD_APP_LINK}" == "1" ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -ft share-download-app-link=true -ft share-download-app-link-url=https://app.scriptoria.io/downloads/apk/${APPDEF_PACKAGE_NAME}/published" - fi - - # if building APK for Google Play, then include data safety CSV in output - if [[ "${TARGETS}" == *"play-listing"* ]]; then - SCRIPT_OPT="${SCRIPT_OPT} -data-safety-csv" - fi - - - process_audio_sources - process_audio_download - - echo "BUILD_NUMBER=${BUILD_NUMBER}" - echo "VERSION_NAME=${VERSION_NAME}" - echo "VERSION_CODE=${VERSION_CODE}" - echo "OUTPUT_DIR=${OUTPUT_DIR}" - echo "SCRIPT_OPT=${SCRIPT_OPT}" - - if [[ "${BUILD_KEYSTORE}" != "" ]]; then - echo "Using build keystore=${BUILD_KEYSTORE}" - SECRETS_SUBDIR="google_play_store/${PUBLISHER}/${BUILD_KEYSTORE}" - sync_build_secrets "${SECRETS_SUBDIR}" - KS="${SECRETS_DIR}/${BUILD_KEYSTORE}.keystore" - else - echo "Using publisher keystore=${PUBLISHER}" - SECRETS_SUBDIR="google_play_store/${PUBLISHER}" - sync_build_secrets "${SECRETS_SUBDIR}" - KS="${SECRETS_DIR}/${PUBLISHER}.keystore" - fi - KSP="$(cat "${SECRETS_DIR}/ksp.txt")" - KA="$(cat "${SECRETS_DIR}/ka.txt")" - KAP="$(cat "${SECRETS_DIR}/kap.txt")" - { echo "-ksp \"${KSP}\"" ; echo "-ka \"${KA}\""; echo "-kap \"${KAP}\""; } >> "${SECRETS_DIR}/keys.txt" - KS_OPT="-ks ${KS} -i ${SECRETS_DIR}/keys.txt" - - echo "KEYSTORE=${KS}" - - cd "$PROJECT_DIR" || exit 1 - - echo "APPBUILDER_SCRIPT_VERSION=${APPBUILDER_SCRIPT_VERSION}" - # If AppBuilder >= 13.3, building AAB will also build APK, so no need for a separate APK build - if [[ "${BUILD_ANDROID_AAB}" == "1" ]] && dpkg --compare-versions "$APPBUILDER_SCRIPT_VERSION" ge "13.3"; then - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -app-bundle ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} - else - # If AppBuilder < 13.3, regular APK build and possible AAB build - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} - if [[ "${BUILD_ANDROID_AAB}" == "1" ]]; then - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build -app-bundle ${KS_OPT} -fp apk.output="$OUTPUT_DIR" -vc "$VERSION_CODE" -vn "$VERSION_NAME" ${SCRIPT_OPT} - fi - fi - - # verify output -- AAPT2 is failing during appbuilder build but error is not getting back to script - pushd "$OUTPUT_DIR" - shopt -s nullglob - for f in *.{apk,aab}; do - echo "JARSIGNER: Checking ${OUTPUT_DIR}/$f" - jarsigner -verify "$f" - done - shopt -u nullglob - popd - - ### - # For the download page, we need the primary color and localized string for "Download APK" - # - # Add primary-color. Look for an overriden value (will return error if not found). If not found, then look in build values (yuck). - set +e - PRIMARY_COLOR=$(xmlstarlet sel -t -v '/app-definition/colors/color[@name = "PrimaryColor"]/color-mapping/@value' "${PROJECT_DIR}/build.appDef" ) || echo "Color not set in appDef" - if [[ "${PRIMARY_COLOR}" == "" ]]; then - # This is a little ugly getting the color after a build - PRIMARY_COLOR=$(xmlstarlet sel -t -v '/resources/color[@name = "colorPrimary"]' "/tmp/App Builder/build/${APP_BUILDER_TLA}.000/a/res/values/colors.xml") - fi - echo "$PRIMARY_COLOR" > "${PROJECT_DIR}/build_data/publish/play-listing/primary-color.txt" - - # Add download-apk-strings.json - # Extract the entries from appDef as XML and then convert to JSON. - # It is possible to have empty entries (e.g. ) which xmlstarlet returns as . - # This requires the first regex to remove these or it will cause problems with the converstion to JSON - DOWNLOAD_STRINGS=$(xmlstarlet sel -t -c '/app-definition/translation-mappings/translation-mapping[@id = "Download_APK"]/translation' "${PROJECT_DIR}/build.appDef" | sed -r 's///g; s//" ,/g; s/>/ : "/g' | sed 's/^/{/; s/,$/}/') - if [[ "${DOWNLOAD_STRINGS}" == "" ]]; then - DOWNLOAD_STRINGS='{"en" : "Download APK"}' - fi - echo "$DOWNLOAD_STRINGS" > "${PROJECT_DIR}/build_data/publish/play-listing/download-apk-strings.json" - set -e -} - -build_html() { - echo "Build html" - echo "OUTPUT_DIR=${OUTPUT_DIR}" - cd "$PROJECT_DIR" || exit 1 - - HTML_OUTPUT_DIR=/tmp/output/html - mkdir -p "${HTML_OUTPUT_DIR}" - if [[ "${BUILD_HTML_COLLECTION_ID}" == *","* ]]; then - IFS=',' read -ra COLLECTIONS <<< "${BUILD_HTML_COLLECTION_ID}" - for i in "${COLLECTIONS[@]}"; do - IFS='=' read -ra PARAMS <<< "${i}" - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -html "${PARAMS[0]}" -p "${PARAMS[1]}" -fp html.output="${HTML_OUTPUT_DIR}" ${SCRIPT_OPT} - done - pushd "${HTML_OUTPUT_DIR}" - else - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -html "${BUILD_HTML_COLLECTION_ID}" -fp html.output="${HTML_OUTPUT_DIR}" ${SCRIPT_OPT} - pushd "${HTML_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}" - fi - zip -r "${OUTPUT_DIR}/html.zip" . - popd - # Not exported so clear it - VERSION_CODE="" - APPDEF_PACKAGE_NAME="" -} - -make_pwa_audio_url_relative() { - AUDIO_URL=$(xmlstarlet sel -t -v "/app-definition/features/feature[@name = 'export-html-audio-path']/@value" "${PROJECT_DIR}/build.appDef" || true) - if [[ "${AUDIO_URL}" == "" ]]; then - echo "Export HTML Audio Path not set" - exit 1 - fi - AUDIO_RELATIVE_URL=$(echo "${AUDIO_URL}" | sed -E 's/^https?://') - xmlstarlet ed --inplace -u "/app-definition/features/feature[@name = 'export-html-audio-path']/@value" -v "${AUDIO_RELATIVE_URL}" "${PROJECT_DIR}/build.appDef" -} - -build_pwa() { - echo "Build pwa" - echo "OUTPUT_DIR=${OUTPUT_DIR}" - cd "$PROJECT_DIR" || exit 1 - - if [[ "${BUILD_PWA_AUDIO_RELATIVE_URL}" == "1" ]]; then - make_pwa_audio_url_relative - fi - PWA_OUTPUT_DIR=/tmp/output/pwa - mkdir -p "${PWA_OUTPUT_DIR}" - if [[ "${BUILD_PWA_COLLECTION_ID}" == *","* ]]; then - IFS=',' read -ra COLLECTIONS <<< "${BUILD_PWA_COLLECTION_ID}" - for i in "${COLLECTIONS[@]}"; do - IFS='=' read -ra PARAMS <<< "${i}" - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -pwa "${PARAMS[0]}" -p "${PARAMS[1]}" -fp html.output="${PWA_OUTPUT_DIR}" ${SCRIPT_OPT} - done - pushd "${PWA_OUTPUT_DIR}" - else - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -pwa "${BUILD_PWA_COLLECTION_ID}" -fp html.output="${PWA_OUTPUT_DIR}" ${SCRIPT_OPT} - pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}" - fi - zip -r "${OUTPUT_DIR}/pwa.zip" . - popd - # Not exported so clear it - VERSION_CODE="" - APPDEF_PACKAGE_NAME="" -} - -build_modern_pwa() { - echo "Build Modern PWA" - echo "OUTPUT_DIR=${OUTPUT_DIR}" - cd "$PROJECT_DIR" || exit 1 - - PWA_OUTPUT_DIR=/tmp/output/pwa - mkdir -p "${PWA_OUTPUT_DIR}" - - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build-modern-pwa -fp pwa.output="${PWA_OUTPUT_DIR}" ${SCRIPT_OPT} - # In 13.4, the output directory changed to not include /build (using rsync instead of cp -r) - if [[ -d "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}/build" ]]; then - pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}/build" - else - pushd "${PWA_OUTPUT_DIR}/${APPDEF_PACKAGE_NAME}" - fi - zip -r "${OUTPUT_DIR}/pwa.zip" . - popd - VERSION_CODE="" - APPDEF_PACKAGE_NAME="" -} - -set_default_asset_package() { - ASSET_FILENAME="${APPDEF_PACKAGE_NAME}.zip" - echo "Updating ipa-app-type=assets" - echo "Updating ipa-asset-filename=${ASSET_FILENAME}" - echo "Project=${PROJECT_DIR}/build.appDef" - if grep -q "" "${PROJECT_DIR}/build.appDef"; then - xmlstarlet ed --inplace -u "/app-definition/ipa-app-type" -v "assets" "${PROJECT_DIR}/build.appDef" - else - xmlstarlet ed --inplace -s "/app-definition" -t elem -n "ipa-app-type" -v "assets" "${PROJECT_DIR}/build.appDef" - fi - if grep -q "" "${PROJECT_DIR}/build.appDef"; then - xmlstarlet ed --inplace -u "/app-definition/ipa-asset-filename" -v "${ASSET_FILENAME}" "${PROJECT_DIR}/build.appDef" - else - xmlstarlet ed --inplace -s "/app-definition" -t elem -n "ipa-asset-filename" -v "${ASSET_FILENAME}" "${PROJECT_DIR}/build.appDef" - fi -} - -build_asset_package() { - echo "Build asset-package" - echo "OUTPUT_DIR=${OUTPUT_DIR}" - cd "$PROJECT_DIR" || exit 1 - - ASSET_OUTPUT_DIR="${OUTPUT_DIR}/asset-package" - mkdir -p "${ASSET_OUTPUT_DIR}" - - APP_TYPE_COUNT=$(xmlstarlet sel -t -v "count(/app-definition/ipa-app-type)" "${PROJECT_DIR}/build.appDef") - ASSET_FILENAME_COUNT=$(xmlstarlet sel -t -v "count(/app-definition/ipa-asset-filename)" "${PROJECT_DIR}/build.appDef") - if [[ "$APP_TYPE_COUNT" == 0 || "$ASSET_FILENAME_COUNT" == 0 ]]; then - # Older project; provide default - set_default_asset_package - else - APP_TYPE=$(xmlstarlet sel -t -v "/app-definition/ipa-app-type" "${PROJECT_DIR}/build.appDef") - if [[ "$APP_TYPE" != "assets" ]]; then - set_default_asset_package - fi - fi - - APP_TYPE=$(xmlstarlet sel -t -v "/app-definition/ipa-app-type" "${PROJECT_DIR}/build.appDef") - ASSET_FILENAME="$(xmlstarlet sel -t -v "//app-definition/ipa-asset-filename" "${PROJECT_DIR}/build.appDef")" - if [[ "$ASSET_FILENAME" == "" ]]; then - set_default_asset_package - fi - APP_NAME="$(xmlstarlet sel -t -v "/app-definition/app-name" "${PROJECT_DIR}/build.appDef")" - echo "APP_TYPE=${APP_TYPE}" - echo "ASSET_FILENAME=${ASSET_FILENAME}" - echo "APP_NAME=${APP_NAME}" - - # shellcheck disable=SC2086 - $APP_BUILDER_SCRIPT_PATH -load build.appDef -no-save -build-assets -fp ipa.output="${ASSET_OUTPUT_DIR}" -vn "$VERSION_NAME" ${SCRIPT_OPT} - - # Build preview - cat >"${ASSET_OUTPUT_DIR}/preview.html" < - -

    Preview

    - - - -EOL - - ### Build notification - - # query langtags - NOTIFY_LANG_TMP=$(mktemp) - jq -cM "{app_lang: .[] | select(.tag==\"${PROJECT_LANGUAGE}\") }" /root/langtags.json > "${NOTIFY_LANG_TMP}" - if [ ! -s "${NOTIFY_LANG_TMP}" ]; then - # The language was not found; provide default - echo '{}' | jq -cM --arg lang "${PROJECT_LANGUAGE}" '. + { app_lang: { tag: $lang } }' > "$NOTIFY_LANG_TMP" - fi - - # build listing - NOTIFY_LISTING_TMP=$(mktemp) - NOTIFY_BASE_TMP=$(mktemp) - PLAY_LISTING_DIR="build_data/publish/play-listing" - echo '{}' | jq -cM '. + { listing: [] }' > "$NOTIFY_BASE_TMP" - pushd "${PLAY_LISTING_DIR}" - NOTIFY_LANGS=$(find . -mindepth 1 -maxdepth 1 -type d | cut -d/ -f2) - for lang in $NOTIFY_LANGS - do - sd="$(cat "$lang/short_description.txt")" - fd="$(cat "$lang/full_description.txt")" - title="$(cat "$lang/title.txt")" - jq -cM --arg lang "$lang" --arg sd "$sd" --arg fd "$fd" --arg title "$title" \ - '.listing += [ { lang: $lang, title: $title, short_description: $sd, full_description: $fd } ]' \ - "$NOTIFY_BASE_TMP" > "$NOTIFY_LISTING_TMP" - cp "$NOTIFY_LISTING_TMP" "$NOTIFY_BASE_TMP" - done - popd - - # extract image - pushd "build_data/images" - # look for ios images - NOTIFY_IMAGES_TMP=$(mktemp) - echo '{}' | jq -cM '. + { image: { files: [] } }' > "$NOTIFY_BASE_TMP" - if [ -d "ios/drawer" ]; then - # copy predefined images - pushd "ios/drawer" - NOTIFY_IMAGES=$(find . -mindepth 1 -maxdepth 1 -type f | cut -d/ -f2) - for image in $NOTIFY_IMAGES - do - size=1x - if [[ "$image" == *"@2x"* ]]; then - size=2x - elif [[ "$image" == *"@3x"* ]]; then - size=3x - fi - jq -cM --arg size "$size" --arg image "$image" '.image.files += [ {size: $size, src: $image} ]' "$NOTIFY_BASE_TMP" > "$NOTIFY_IMAGES_TMP" - cp "$image" "${ASSET_OUTPUT_DIR}/${image}" - cp "$NOTIFY_IMAGES_TMP" "$NOTIFY_BASE_TMP" - done - popd - else - # resize largest image to desired sizes - for size in "xxxhdpi" "xxhdpi" "xhdpi" "hdpi" - do - image_file="drawable-${size}/nav_drawer.png" - if [ -f "${image_file}" ]; then - convert "${image_file}" -resize 750x422 "${ASSET_OUTPUT_DIR}/nav_drawer@3x.png" - convert "${image_file}" -resize 500x282 "${ASSET_OUTPUT_DIR}/nav_drawer@2x.png" - convert "${image_file}" -resize 250x141 "${ASSET_OUTPUT_DIR}/nav_drawer.png" - jq -cM '.image.files += [ { size: "1x", src: "nav_drawer.png" }, { size: "2x", src: "nav_drawer@2x.png" }, {size: "3x", src: "nav_drawer@3x.png" } ]' "$NOTIFY_BASE_TMP" > "$NOTIFY_IMAGES_TMP" - break - fi - done - fi - popd - - # combine json objects - if [ -s "${NOTIFY_IMAGES_TMP}" ]; then - jq -cM -s '.[0] * .[1] * .[2]' "$NOTIFY_LANG_TMP" "$NOTIFY_IMAGES_TMP" "$NOTIFY_LISTING_TMP" > "${ASSET_OUTPUT_DIR}/notify.json" - else - jq -cM -s '.[0] * .[1]' "$NOTIFY_LANG_TMP" "$NOTIFY_LISTING_TMP" > "${ASSET_OUTPUT_DIR}/notify.json" - fi - - # Not exported so clear it - VERSION_CODE="" -} - -download_play_listing() { - # For existing apps being added to Scriptoria, we may need to download the Play Store listing information - if [[ "${BUILD_DOWNLOAD_PLAY_LISTING}" == "1" ]]; then - if [[ "${PUBLISHER}" != "" && "${APPDEF_PACKAGE_NAME}" != "" ]]; then - echo "Downloading existing play listing for Publisher:${PUBLISHER}, Package Name:${APPDEF_PACKAGE_NAME}" - SECRETS_SUBDIR="google_play_store/${PUBLISHER}" - sync_publish_secrets "${SECRETS_SUBDIR}" - export SUPPLY_JSON_KEY="${SECRETS_DIR}/playstore_api.json" - export SUPPLY_PACKAGE_NAME="${APPDEF_PACKAGE_NAME}" - DOWNLOAD_TMP_DIR=$(mktemp -d) - DOWNLOAD_TARGET_DIR="build_data/publish/play-listing" - export SUPPLY_METADATA_PATH="${DOWNLOAD_TMP_DIR}/metadata" - - fastlane supply init - - # Ensure metadata dir exists and remove any default files/dirs AppBuilder may have created. - mkdir -p "${DOWNLOAD_TARGET_DIR}" - rm -rf -- "${DOWNLOAD_TARGET_DIR:?}"/* - cp -a "${SUPPLY_METADATA_PATH}/." "${DOWNLOAD_TARGET_DIR}/" - - (cd "${SUPPLY_METADATA_PATH}" && zip -r "${OUTPUT_DIR}/play-listing.zip" .) - fi - fi -} - -build_play_listing() { - echo "Build play listing" - echo "BUILD_NUMBER=${BUILD_NUMBER}" - echo "VERSION_NAME=${VERSION_NAME}" - echo "VERSION_CODE=${VERSION_CODE}" - echo "OUTPUT_DIR=${OUTPUT_DIR}" - cd "$PROJECT_DIR" || exit 1 - - download_play_listing - - APK_FILES=("${OUTPUT_DIR}"/*.apk) - AAPT="$(find /opt/android-sdk/build-tools -name aapt | head -n 1)" - - if [ -f "build_data/about/about.txt" ]; then - cp build_data/about/about.txt "$OUTPUT_DIR"/ - fi - PUBLISH_DIR="build_data/publish" - PLAY_LISTING_DIR="${PUBLISH_DIR}/play-listing" - LIST_DIR="${PLAY_LISTING_DIR}/" - MANIFEST_FILE="manifest.txt" - if [ -f $LIST_DIR$MANIFEST_FILE ]; then - rm $LIST_DIR$MANIFEST_FILE - fi - find $PLAY_LISTING_DIR -type f | while read -r f - do - fn=${f#*"$PLAY_LISTING_DIR/"} - echo "$fn" >> "$OUTPUT_DIR"/$MANIFEST_FILE - done - if [ -d "$PLAY_LISTING_DIR" ]; then - cp -r "$PLAY_LISTING_DIR" "$OUTPUT_DIR" - find "$OUTPUT_DIR" -name whats_new.txt | while read -r filename - do - DIR=$(dirname "${filename}") - cp "$filename" "$OUTPUT_DIR" - mkdir "${DIR}/changelogs" - if [ "${#APK_FILES[@]}" -gt 1 ]; then - # If there are multiple APK files, we only need to provide 1 changelog, - # but it has to match one of the version codes. - apk=${APK_FILES[0]} - APK_VERSION_CODE=$($AAPT dump badging "${apk}" | grep "^package" | sed -n "s/.*versionCode='\([0-9]*\).*/\1/p") - cp "$filename" "${DIR}/changelogs/${APK_VERSION_CODE}.txt" - else - cp "$filename" "${DIR}/changelogs/${VERSION_CODE}.txt" - fi - done - fi -} - -build_gradle() { - echo "Gradle $1" - if [ -f "${PROJECT_DIR}/build.gradle" ]; then - pushd "$PROJECT_DIR" || exit 1 - gradle "$1" - popd || exit 1 - elif [ -f "${SCRIPT_DIR}/build.gradle" ]; then - pushd "$SCRIPT_DIR" || exit 1 - gradle "$1" - popd || exit 1 - fi -} - -prepare_appbuilder_dir() { - # Ensure 'App Projects' directory - if [[ "${APP_BUILDER_SCRIPT_PATH}" == "scripture-app-builder" ]]; then - APP_BUILDER_TLA=SAB - APP_BUILDER_FOLDER="Scripture Apps" - elif [[ "${APP_BUILDER_SCRIPT_PATH}" == "reading-app-builder" ]]; then - APP_BUILDER_TLA=RAB - APP_BUILDER_FOLDER="Reading Apps" - elif [[ "${APP_BUILDER_SCRIPT_PATH}" == "dictionary-app-builder" ]]; then - APP_BUILDER_TLA=DAB - APP_BUILDER_FOLDER="Dictionary Apps" - elif [[ "${APP_BUILDER_SCRIPT_PATH}" == "keyboard-app-builder" ]]; then - APP_BUILDER_TLA=KAB - APP_BUILDER_FOLDER="Keyboard Apps" - fi - - mkdir -p "${HOME}/App Builder/${APP_BUILDER_FOLDER}/App Projects" - - if [[ "${BUILD_KEYS_FILE}" != "" ]]; then - KEY_DEST_DIR="${HOME}/App Builder/${APP_BUILDER_FOLDER}" - mkdir -p "${KEY_DEST_DIR}" - cp "${PROJECT_DIR}/build_data/${BUILD_KEYS_FILE}" "${KEY_DEST_DIR}/keys.txt" - fi -} - -prepare_appbuilder_project() { - # In the past, we have had problems with multiple .appDef files being checked in and confusing error. - # Fail quickly in this situation - cd "$PROJECT_DIR" || exit 1 - PROJ_COUNT=$(find . -maxdepth 1 -name "*.appDef" | wc -l) - if [[ "$PROJ_COUNT" -ne "1" ]]; then - echo "ERROR: Wrong number of projects: ${PROJ_COUNT}" - exit 2 - fi - - APPBUILDER_SCRIPT_VERSION=$($APP_BUILDER_SCRIPT_PATH -? | grep "Version" | cut -d\ -f2) - - PROJ_NAME=$(basename -- *.appDef .appDef) - PROJ_DIR=$(find . -maxdepth 1 -type d | grep -i -F "${PROJ_NAME}_data") - if [[ -f "${PROJ_NAME}.appDef" && -d "${PROJ_DIR}" ]]; then - echo "Moving ${PROJ_NAME}.appDef and ${PROJ_DIR}" - mv "${PROJ_NAME}.appDef" build.appDef - mv "${PROJ_DIR}" build_data - else - echo "ERROR: Project appDef or project data not found" - exit 3 - fi - - PUBLISH_PROPERTIES="build_data/publish/properties.json" - if [ -f "${PUBLISH_PROPERTIES}" ]; then - # Handle spaces in properties values - # https://stackoverflow.com/a/48513046/35577 - values=$(cat "${PUBLISH_PROPERTIES}") - while read -rd $'' line - do - echo "exporting ${line}" - export "${line?}" - done < <(jq -r <<<"$values" 'to_entries|map("\(.key)=\(.value)\u0000")[]') - - OUTPUT_PUBLISH_PROPERTIES="${OUTPUT_DIR}/publish-properties.json" - # Add addition properties - PUBLISH_SE_RECORD="build_data/publish/se-record.json" - if [[ -f "${PUBLISH_SE_RECORD}" && "$(jq -r '. | length' "${PUBLISH_SE_RECORD}")" == "1" ]]; then - # if there is at least one Scripture Earth record - # Note: it is possible to have the record, but not the notify property -- "|| true" eats the error and still returns blank - PUBLISH_NOTIFY_SCRIPTURE_EARTH=$(xmlstarlet sel -t -v "/app-definition/publishing/scripture-earth/@notify" build.appDef || true) - PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID=$(jq -r '.["0"].relationships.idx' "${PUBLISH_SE_RECORD}") - if [[ "${PUBLISH_NOTIFY_SCRIPTURE_EARTH}" == "true" ]]; then - SCRIPTURE_EARTH_DESCRIPTION=$(xmlstarlet sel -t -v "/app-definition/publishing/scripture-earth/@description" build.appDef || echo "") - echo "Notify Scripture Earth: id=${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID} with description='${SCRIPTURE_EARTH_DESCRIPTION}'" - # If the "Notify Scripture Earth" property is enabled in the AppDef - PUBLISH_TMP=$(mktemp) - PUBLISH_NOTIFY_TYPE=$(jq -r '.PUBLISH_NOTIFY | type' "${PUBLISH_PROPERTIES}") - # We are currently only supporting notifying Scripture Earth of product updates. - # However, Kalaam has expressed interested in being notified as well. This would - # allow Kalaam to add a PUBLISH_NOTIFY publishing property. We would also have to - # implement something in publish.sh to notify their server correctly. - if [[ "${PUBLISH_NOTIFY_TYPE}" == "null" ]]; then - # There is no property so set it (as an array) to Scripture Earth entry - jq -cM '.PUBLISH_NOTIFY += "SCRIPTURE_EARTH"' "${PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" - elif [ "${PUBLISH_NOTIFY_TYPE}" == "string" ]; then - # There is an existing property so convert to an array and add to Scripture Earth entry. - # We are only going to deal with one item being there. If there will be multiple, then we - # will have to split the string and create an array of the results - PUBLISH_NOTIFY_CURRENT=$(jq -r '.PUBLISH_NOTIFY' "${PUBLISH_PROPERTIES}") - jq -cM --arg cur "${PUBLISH_NOTIFY_CURRENT}" '.PUBLISH_NOTIFY += ",SCRIPTURE_EARTH"' "${PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" - fi - cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" - jq -cM --arg idx "${PUBLISH_NOTIFY_SCRIPTURE_EARTH_ID}" --arg desc "${SCRIPTURE_EARTH_DESCRIPTION}" '.SCRIPTURE_EARTH_ID = $idx | .SCRIPTURE_EARTH_DESCRIPTION = $desc' "${OUTPUT_PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" - cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" - fi - fi - - # If modern-pwa, then update the subdirectory configuration from the rclone publish path if not defined - for target in $TARGETS; do - if [ "$target" = "modern-pwa" ]; then - INPUT_PUBLISH_PROPERTIES=$PUBLISH_PROPERTIES - if [ -f "${OUTPUT_PUBLISH_PROPERTIES}" ]; then - INPUT_PUBLISH_PROPERTIES=$OUTPUT_PUBLISH_PROPERTIES - fi - if jq -e '.PUBLISH_CLOUD_REMOTE_PATH' "${INPUT_PUBLISH_PROPERTIES}" >/dev/null; then - if ! xmlstarlet sel -t -v "count(/app-definition//pwa-sub-directory)" build.appDef 2>/dev/null; then - # Note: The #/ in the variable expansion removes any leading slashes if it exists. - # This makes sure the string begins with a single slash - PWA_SUBDIR="/${PUBLISH_CLOUD_REMOTE_PATH#/}" - echo "PUBLISH_CLOUD_REMOTE_PATH exists, but PWA Sub Directory is missing." - echo "PUBLISH_CLOUD_REMOTE_PATH=${PUBLISH_CLOUD_REMOTE_PATH} so update PWA Sub Directory=${PWA_SUBDIR}" - APPDEF_TMP=$(mktemp) - xmlstarlet ed \ - -s "/app-definition" -t elem -n "pwa-sub-directory" -v "${PWA_SUBDIR}" \ - build.appDef > "${APPDEF_TMP}" - cp "${APPDEF_TMP}" build.appDef - fi - else - PWA_SUBDIR=$(xmllint --xpath "/app-definition//pwa-sub-directory/text()" build.appDef 2>/dev/null || echo "") - if [ "$PWA_SUBDIR" != "" ]; then - echo "PUBLISH_CLOUD_REMOTE_PATH does not exist, but PWA Sub Directory is set." - echo "PWA Sub Directory=${PWA_SUBDIR} so update PUBLISH_CLOUD_REMOTE_PATH=${PWA_SUBDIR#/}" - PUBLISH_TMP=$(mktemp) - jq -cM ".PUBLISH_CLOUD_REMOTE_PATH += \"${PWA_SUBDIR#/}\"" "${INPUT_PUBLISH_PROPERTIES}" > "${PUBLISH_TMP}" - cp "${PUBLISH_TMP}" "${OUTPUT_PUBLISH_PROPERTIES}" - fi - fi - fi - done - - if [ ! -f "${OUTPUT_PUBLISH_PROPERTIES}" ]; then - # if no Scripture Earth record, then copy straight as normal - cp "${PUBLISH_PROPERTIES}" "${OUTPUT_PUBLISH_PROPERTIES}" - fi - - cat "${OUTPUT_PUBLISH_PROPERTIES}" - fi - - APPDEF_VERSION_NAME=$(xmllint --xpath "string(/app-definition/version/@name)" build.appDef) - echo "APPDEF_VERSION_NAME=${APPDEF_VERSION_NAME}" - echo "BUILD_MANAGE_VERSION_NAME=${BUILD_MANAGE_VERSION_NAME}" - if [[ "${BUILD_MANAGE_VERSION_NAME}" == "0" ]]; then - VERSION_NAME=${APPDEF_VERSION_NAME} - else - VERSION_NAME=$("${APP_BUILDER_SCRIPT_PATH}" -? | grep 'Version' | awk -F '[ +]' '{print $2}') - fi - - APPDEF_PACKAGE_NAME=$(xmllint --xpath "/app-definition/package/text()" build.appDef) - echo "APPDEF_PACKAGE_NAME=${APPDEF_PACKAGE_NAME}" - - APPDEF_VERSION_CODE=$(xmllint --xpath "string(/app-definition/version/@code)" build.appDef) - echo "APPDEF_VERSION_CODE=${APPDEF_VERSION_CODE}" - echo "BUILD_MANAGE_VERSION_CODE=${BUILD_MANAGE_VERSION_CODE}" - if [[ "${BUILD_MANAGE_VERSION_CODE}" == "0" ]]; then - VERSION_CODE=$((APPDEF_VERSION_CODE)) - else - if [[ "$APPDEF_VERSION_CODE" -gt "$VERSION_CODE" ]]; then VERSION_CODE=$((APPDEF_VERSION_CODE)); fi - fi -} - -complete_successful_build() { - if [[ "${APPDEF_PACKAGE_NAME}" != "" ]]; then - echo "${APPDEF_PACKAGE_NAME}" > "$OUTPUT_DIR"/package_name.txt - fi - if [[ "${VERSION_CODE}" != "" ]]; then - echo "${VERSION_CODE}" > "$OUTPUT_DIR"/version_code.txt - echo "{ \"version\" : \"${VERSION_NAME} (${VERSION_CODE})\", \"versionName\" : \"${VERSION_NAME}\", \"versionCode\" : \"${VERSION_CODE}\", \"appbuilderVersion\": \"${APPBUILDER_SCRIPT_VERSION}\" } " > "$OUTPUT_DIR"/version.json - else - echo "{ \"version\" : \"${VERSION_NAME}\", \"versionName\" : \"${VERSION_NAME}\", \"appbuilderVersion\": \"${APPBUILDER_SCRIPT_VERSION}\" } " > "$OUTPUT_DIR"/version.json - fi - - echo "ls -lR ${OUTPUT_DIR}" - ls -lR "${OUTPUT_DIR}" -} - -env | sort -prepare_appbuilder_project -prepare_appbuilder_dir - -echo "TARGETS: $TARGETS" -for target in $TARGETS -do - case "$target" in - "apk") build_apk ;; - "asset-package") build_asset_package ;; - "play-listing") build_play_listing ;; - "html") build_html ;; - "pwa") build_pwa ;; - "modern-pwa") build_modern_pwa ;; - *) build_gradle "$target" ;; - esac -done - -complete_successful_build diff --git a/application/console/views/cron/scripts/upload/default/publish.sh b/application/console/views/cron/scripts/upload/default/publish.sh deleted file mode 100644 index d160662c..00000000 --- a/application/console/views/cron/scripts/upload/default/publish.sh +++ /dev/null @@ -1,512 +0,0 @@ -#!/usr/bin/env bash -shopt -s nullglob -set -e -o pipefail -set -x -LOG_FILE="${OUTPUT_DIR}"/console.log -exec > >(tee "${LOG_FILE}") 2>&1 - -# Scriptoria 1 uses UI_URL and Scriptoria 2 uses ORIGIN -export SERVER_URL="${ORIGIN:-${UI_URL:-}}" - -export PATH="$HOME/.rbenv/bin:$PATH" -eval "$(rbenv init -)" -sync_secrets() { - SECRETS_SUBDIR=$1 - SECRETS_S3="s3://${SECRETS_BUCKET}/jenkins/publish" - echo "sync secrets" - echo "SECRETS_SUBDIR = ${SECRETS_SUBDIR}" - aws s3 sync "${SECRETS_S3}/${SECRETS_SUBDIR}" "${SECRETS_DIR}" -} - -publish_google_play() { - fastlane env - echo "OUTPUT_DIR=${OUTPUT_DIR}" - SECRETS_SUBDIR="google_play_store/${PUBLISHER}" - sync_secrets "${SECRETS_SUBDIR}" - export SUPPLY_JSON_KEY="${SECRETS_DIR}/playstore_api.json" - cd "$ARTIFACTS_DIR" || exit 1 - PACKAGE_NAME="$(cat package_name.txt)" - export SUPPLY_PACKAGE_NAME="${PACKAGE_NAME}" - VERSION_CODE="$(cat version_code.txt)" - export SUPPLY_METADATA_PATH="play-listing" - - if [[ -n "${PUBLISH_NO_APK}" ]]; then - export SUPPLY_SKIP_UPLOAD_APK=true - export SUPPLY_SKIP_UPLOAD_AAB=true - echo "APK: None (PUBLISH_NO_APK=1), just republish" - elif [[ "${#AAB_FILES[@]}" -gt 0 ]]; then - export SUPPLY_AAB="${AAB_FILES[0]}" - export SUPPLY_SKIP_UPLOAD_APK=true - echo "AAB: ${SUPPLY_AAB}" - elif [[ "${#APK_FILES[@]}" -gt 1 ]]; then - # Build a comma-separated list of files - SUPPLY_APK_PATHS=$(find . -name "*.apk" | tr '\n' ',') - export SUPPLY_APK_PATHS - echo "APKs: ${SUPPLY_APK_PATHS}" - elif [[ "${#APK_FILES[@]}" -gt 0 ]]; then - export SUPPLY_APK="${APK_FILES[0]}" - echo "APK: ${SUPPLY_APK}" - else - echo "APK: Missing AAB or APK!" - exit 1 - fi - - if [[ -n "${PUBLISH_GOOGLE_PLAY_DRAFT}" ]]; then - echo "Publishing Draft" - export SUPPLY_RELEASE_STATUS=draft - # On the initial publish, a user has to create the app store entry and upload the APK - # to associate the entry with the package name and keystore - # Google Play APIs have changed so that we can't re-upload the APK. It gives a duplicate - # version code error. So we need to skip uploading APK that was uploaded by the user - if [[ "${VERSION_CODE}" == "${PUBLISH_GOOGLE_PLAY_UPLOADED_VERSION_CODE}" ]]; then - if [[ "${BUILD_NUMBER}" != "${PUBLISH_GOOGLE_PLAY_UPLOADED_BUILD_ID}" ]]; then - echo "ERROR: Duplicate version code used on different builds during initial publish" - exit 1 - fi - echo "Not publishing APK(s) or AAB ... uploaded by user" - export SUPPLY_SKIP_UPLOAD_APK=true - export SUPPLY_SKIP_UPLOAD_AAB=true - else - echo "Publishing APK(s) ... rebuilt after first uploaded by user" - fi - - fi - - # https://stackoverflow.com/a/23357277/35577 - CHANGELOGS=() - if [[ -z "${PUBLISH_NO_APK}" ]]; then - while IFS= read -r -d $'\0'; do - CHANGELOGS+=("$REPLY") - done < <(find "${ARTIFACTS_DIR}"/play-listing -name "[0-9]*.txt" -print0 | grep -FzZ 'changelogs') - fi - - if [[ "${#CHANGELOGS[@]}" -gt 0 ]]; then - APK_VERSION_CODE="$(basename "${CHANGELOGS[0]%.txt}")" - export SUPPLY_VERSION_CODE="${APK_VERSION_CODE}" - else - export SUPPLY_SKIP_UPLOAD_CHANGELOGS=true - fi - - if [ -n "$PROMOTE_FROM" ]; then - export SUPPLY_TRACK="${PROMOTE_FROM}" - export SUPPLY_TRACK_PROMOTE_TO="${CHANNEL}" - else - export SUPPLY_TRACK="${CHANNEL}" - fi - - # https://github.com/fastlane/fastlane/issues/21507 - # Google Api Error: Unathorized - Request is missing required authentication credential - # Retry to work-around - # if [ -z "${SUPPLY_UPLOAD_MAX_RETRIES}" ]; then - # export SUPPLY_UPLOAD_MAX_RETRIES=5 - # fi - - env | grep "SUPPLY_" - fastlane supply - if [[ -n "${PUBLISH_NO_APK}" ]]; then - PUBLISH_SIZE=0 - PERMALINK_URL="" - else - PUBLISH_SIZE="$(stat --format="%s" "${APK_FILES[0]}")" - PERMALINK_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/apk" - fi - PUBLISH_URL="https://play.google.com/store/apps/details?id=${PACKAGE_NAME}" - echo "${PUBLISH_URL}" > "${OUTPUT_DIR}/publish_url.txt" - echo "ls -l ${OUTPUT_DIR}" - ls -l "${OUTPUT_DIR}" -} - -publish_s3_bucket() { - ZIP_FILES=( "${ARTIFACTS_DIR}"/asset-package/*.zip ) - - SECRETS_SUBDIR="s3_bucket/${PUBLISHER}" - sync_secrets "${SECRETS_SUBDIR}" - CREDENTIALS="${SECRETS_DIR}/credentials" - CONFIG="${SECRETS_DIR}/config" - DEST_BUCKET_PATH=$(cat "${SECRETS_DIR}/bucket") - if [[ "${DEST_BUCKET_PATH}" == "" ]]; then - echo "bucket file for S3 ${PUBLISHER} is empty!" - exit 1 - fi - - # add the product id to make it unique - if [[ "${PRODUCT_ID}" != "" && "${PUBLISH_S3_ROOT}" == "" ]]; then - DEST_BUCKET_PATH="${DEST_BUCKET_PATH}/${PRODUCT_ID}" - fi - - IFS=/ read -r DEST_BUCKET DEST_PATH <<< "${DEST_BUCKET_PATH}" - - # Detect tye type of publish: apk or asset-package - if [[ "${#APK_FILES[@]}" -gt 0 ]]; then - # apk: publish all the apks - PUBLISH_S3_INCLUDE="*.apk" - PUBLISH_S3_SOURCH_PATH="${ARTIFACTS_DIR}" - PERMALINK_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/apk" - SRC_FILE="${APK_FILES[0]}" - elif [[ "${#ZIP_FILES[@]}" -gt 0 ]]; then - # asset-package: publish all the zip files - PUBLISH_S3_INCLUDE="*.zip" - PUBLISH_S3_SOURCH_PATH="${ARTIFACTS_DIR}/asset-package" - PERMALINK_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/asset-package" - SRC_FILE="${ZIP_FILES[0]}" - fi - PUBLISH_SIZE="$(stat --format="%s" "${SRC_FILE}")" - DEST_FILE="$(basename "${SRC_FILE}")" - - if [[ "${DEST_PATH}" == "" ]]; then - PUBLISH_BASE_URL="https://${DEST_BUCKET}.s3.amazonaws.com" - else - PUBLISH_BASE_URL="https://${DEST_BUCKET}.s3.amazonaws.com/${DEST_PATH}" - fi - PUBLISH_URL="${PUBLISH_BASE_URL}/${DEST_FILE}" - - echo "CREDENTIALS=${CREDENTIALS}" - echo "CONFIG=${CONFIG}" - echo "SRC_FILE=${SRC_FILE}" - echo "PUBLISH_S3_SOURCH_PATH=${PUBLISH_S3_SOURCH_PATH}" - echo "PUBLISH_S3_INCLUDE=${PUBLISH_S3_INCLUDE}" - echo "DEST_BUCKET_PATH=${DEST_BUCKET_PATH}" - echo "DEST_FILE=${DEST_FILE}" - echo "PUBLISH_URL=${PUBLISH_URL}" - - NOTIFY_ASSET_BASE_JSON="${ARTIFACTS_DIR}/asset-package/notify.json" - if [ -f "${NOTIFY_ASSET_BASE_JSON}" ]; then - NOTIFY_ASSET_JSON_TMP=$(mktemp) - # set the baseurl for the images - jq -cM --arg baseurl "${PUBLISH_BASE_URL}" '.image += { baseurl: $baseurl }' "${NOTIFY_ASSET_BASE_JSON}" > "${NOTIFY_ASSET_JSON_TMP}" - jq \ - --arg project_url "${PROJECT_URL}" \ - --arg project_name "${PROJECT_NAME}" \ - --arg publish_url "${PUBLISH_URL}" \ - --arg permalink_url "${PERMALINK_URL}" \ - --arg size "${PUBLISH_SIZE}" \ - --arg app_builder "${APP_BUILDER_SCRIPT_PATH}" \ - --arg app_builder_version "${APP_BUILDER_VERSION}" \ - '. + { project_url: $project_url, project_name: $project_name, publish_url: $publish_url, permalink_url: $permalink_url, size: $size, app_builder: $app_builder, app_builder_version: $app_builder_version}' "${NOTIFY_ASSET_JSON_TMP}" > "${OUTPUT_DIR}/asset-notify.json" - fi - - AWS_SHARED_CREDENTIALS_FILE="${CREDENTIALS}" AWS_CONFIG_FILE="${CONFIG}" aws s3 cp "${PUBLISH_S3_SOURCH_PATH}" "s3://${DEST_BUCKET_PATH}" --acl public-read --acl bucket-owner-full-control --recursive --exclude "*" --include "${PUBLISH_S3_INCLUDE}" --include "*.png" --include "*.json" - - echo "${PUBLISH_URL}" > "${OUTPUT_DIR}/publish_url.txt" -} - -publish_rclone() { - SECRETS_SUBDIR="rclone/${PUBLISHER}" - sync_secrets "${SECRETS_SUBDIR}" - CONFIG="${SECRETS_DIR}/rclone.conf" - RCLONE="rclone -v --config ${CONFIG}" - if [[ "${PUBLISH_CLOUD_REMOTE}" == "" ]]; then - PUBLISH_CLOUD_REMOTE=$(${RCLONE} config dump | jq -r 'keys_unsorted|.[0]') - if [[ "${PUBLISH_CLOUD_REMOTE}" == "" ]]; then - echo "ERROR: No PUBLISH_CLOUD_REMOTE or default remote in ${CONFIG}" - exit 2 - fi - fi - - # Detect the type of publish: apk, html, pwa - APK_COUNT=$(find "${ARTIFACTS_DIR}" -name "*.apk" | wc -l) - PUBLISH_FILE="" - if [[ ${APK_COUNT} -gt 0 ]]; then - # apk: publish all the apks - PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}/\*.apk" - elif [[ -f "${ARTIFACTS_DIR}/pwa.zip" ]]; then - # pwa: unzip the files to a directory and push the directory - mkdir "${ARTIFACTS_DIR}/pwa" - unzip "${ARTIFACTS_DIR}/pwa.zip" -d "${ARTIFACTS_DIR}/pwa" - PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}/pwa" - PUBLISH_FILE="" - elif [[ -f "${ARTIFACTS_DIR}/html.zip" ]]; then - # html: unzip the files to a directory and push the directory - mkdir "${ARTIFACTS_DIR}/html" - unzip "${ARTIFACTS_DIR}/html.zip" -d "${ARTIFACTS_DIR}/html" - PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}/html" - PUBLISH_FILE="index.html" - else - # Fallback to publishing all artifacts - PUBLISH_CLOUD_SOURCE_PATH="${ARTIFACTS_DIR}" - PUBLISH_CLOUD_BACKUP=0 - fi - - if [[ "${PUBLISH_CLOUD_COMMAND}" == "" ]]; then - PUBLISH_CLOUD_COMMAND=sync - fi - - if [[ "${PUBLISH_CLOUD_REMOTE_PATH}" == "" ]]; then - echo "ERROR: PUBLISH_CLOUD_REMOTE_PATH is not set" - exit 2 - fi - - PUBLISH_CLOUD_REMOTE_PATH_ALLOWED=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].remote_path_allowed") - - if [[ "${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED}" != "" ]]; then - [[ ${PUBLISH_CLOUD_REMOTE_PATH} =~ ${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED} ]] - if [[ "${#BASH_REMATCH[0]}" == "0" ]]; then - echo "ERROR: PUBLISH_CLOUD_REMOTE_PATH=${PUBLISH_CLOUD_REMOTE_PATH} doesn't match PUBLISH_CLOUD_REMOTE_PATH_ALLOWED=${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED}" - exit 2 - fi - fi - - if [[ "${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" == "" ]]; then - PUBLISH_CLOUD_BACKUP_REMOTE_PATH="backups" - fi - - # Add after the PUBLISH_CLOUD_REMOTE_PATH_ALLOWED has been check against PUBLISH_CLOUD_REMOTE_PATH - PUBLISH_CLOUD_REMOTE_ROOT=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].remote_root") - if [[ "${PUBLISH_CLOUD_REMOTE_ROOT}" == "null" ]]; then - # should be blank or end with slash so that it can be safely inserted below - PUBLISH_CLOUD_REMOTE_ROOT="" - else - #ensure that it ends with a slash - PUBLISH_CLOUD_REMOTE_ROOT="${PUBLISH_CLOUD_REMOTE_ROOT%/}/" - fi - - echo "PUBLISH_CLOUD_SOURCE_PATH=${PUBLISH_CLOUD_SOURCE_PATH}" - echo "PUBLISH_CLOUD_REMOTE=${PUBLISH_CLOUD_REMOTE}" - echo "PUBLISH_CLOUD_REMOTE_ROOT=${PUBLISH_CLOUD_REMOTE_ROOT}" - echo "PUBLISH_CLOUD_REMOTE_PATH=${PUBLISH_CLOUD_REMOTE_PATH}" - echo "PUBLISH_CLOUD_REMOTE_PATH_ALLOWED=${PUBLISH_CLOUD_REMOTE_PATH_ALLOWED}" - echo "PUBLISH_CLOUD_BACKUP=${PUBLISH_CLOUD_BACKUP}" - echo "PUBLISH_CLOUD_BACKUP_ZIP=${PUBLISH_CLOUD_BACKUP_ZIP}" - echo "PUBLISH_CLOUD_BACKUP_REMOTE_PATH=${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" - - # if there are files to backup and backup is requested... - set +e - BACKUP_FILE_COUNT=$(${RCLONE} size "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" --json 2>/dev/null | jq -r ".count") - set -e - echo "Current file count: ${BACKUP_FILE_COUNT}" - if [[ "${PUBLISH_CLOUD_BACKUP}" == "1" && ${BACKUP_FILE_COUNT} -gt 0 ]]; then - DATE=$(date -u +"%Y-%m-%d_%H-%M-%S") - if [[ "${PUBLISH_CLOUD_BACKUP_ZIP}" == "1" && "${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" != "" ]]; then - # When performing a zip backup, we have to do download the current files to zip them and then re-upload them - # It is likely that the new files are similar to the old ones. So we will: - # 1. Sync the new files to the backup directory (seed the directory so only reverse diffs are copied) - ${RCLONE} sync "${PUBLISH_CLOUD_SOURCE_PATH}" "${ARTIFACTS_DIR}/Backup" - # 2. Sync the current files to the backup directory - ${RCLONE} sync "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" "${ARTIFACTS_DIR}/Backup" - # 3. Zip the files - BACKUP_FILENAME="$(basename "${PUBLISH_CLOUD_REMOTE_PATH}")-${DATE}.zip" - pushd "${ARTIFACTS_DIR}/Backup" - zip -qr ../"${BACKUP_FILENAME}" -- * - popd - # 4. Copy the backup to the Backup path - ${RCLONE} mkdir "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}" - ${RCLONE} copy "${ARTIFACTS_DIR}/${BACKUP_FILENAME}" "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}/${PUBLISH_CLOUD_REMOTE_PATH}" - else - ${RCLONE} mkdir "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}/${PUBLISH_CLOUD_REMOTE_PATH}/${DATE}" - ${RCLONE} copy "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_BACKUP_REMOTE_PATH}/${PUBLISH_CLOUD_REMOTE_PATH}/${DATE}" - fi - fi - - ${RCLONE} mkdir "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" - # shellcheck disable=SC2086 - ${RCLONE} ${PUBLISH_CLOUD_COMMAND} -u "${PUBLISH_CLOUD_SOURCE_PATH}" "${PUBLISH_CLOUD_REMOTE}:${PUBLISH_CLOUD_REMOTE_ROOT}${PUBLISH_CLOUD_REMOTE_PATH}" - - PUBLISH_BASE_URL=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].public_url") - if [[ "${PUBLISH_BASE_URL}" == "null" ]]; then - echo "ERROR: No public_url for rclone config ${PUBLISH_CLOUD_REMOTE}" - exit 2 - fi - PUBLISH_SERVER_PATH_ROOT=$(${RCLONE} config dump | jq -r ".[\"${PUBLISH_CLOUD_REMOTE}\"].server_root") - PUBLISH_REMOTE_PATH="${PUBLISH_CLOUD_REMOTE_PATH}" - if [[ "${PUBLISH_SERVER_PATH_ROOT}" != "null" ]]; then - PUBLISH_REMOTE_PATH=${PUBLISH_REMOTE_PATH//$PUBLISH_SERVER_PATH_ROOT\//} - fi - if [[ "${PUBLISH_REMOTE_PATH}" == /* ]]; then - # If PUBLISH_REMOTE_PATH starts with a slash, remove it to avoid double slashes in the URL - PUBLISH_REMOTE_PATH="${PUBLISH_REMOTE_PATH:1}" - fi - PUBLISH_URL="${PUBLISH_BASE_URL}/${PUBLISH_REMOTE_PATH}/${PUBLISH_FILE}" - echo "${PUBLISH_URL}" > "${OUTPUT_DIR}/publish_url.txt" - - CLOUDFLARE_PURGE_CONFIG="${SECRETS_DIR}/cloudflare_purge.json" - if [ -f "${CLOUDFLARE_PURGE_CONFIG}" ]; then - CLOUDFLARE_ZONE=$(jq -r '.zone' "${CLOUDFLARE_PURGE_CONFIG}") - CLOUDFLARE_API_KEY=$(jq -r '.key' "${CLOUDFLARE_PURGE_CONFIG}") - - curl --request POST \ - --url "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE}/purge_cache" \ - --header 'Content-Type: application/json' \ - --header "Authorization: Bearer ${CLOUDFLARE_API_KEY}" \ - --data "{ \"files\" : [ \"${PUBLISH_URL}\" ]}" - fi -} - -publish_gradle() { - echo "Gradle $1" - if [ -f "${PROJECT_DIR}/publish.gradle" ]; then - pushd "$PROJECT_DIR" || exit 1 - gradle "$1" - popd || exit 1 - elif [ -f "${SCRIPT_DIR}/publish.gradle" ]; then - pushd "$SCRIPT_DIR" || exit 1 - gradle "$1" - popd || exit 1 - fi -} - -prepare_publish() { - PUBLISH_PROPERTIES="${ARTIFACTS_DIR}/publish-properties.json" - if [[ -f "${PUBLISH_PROPERTIES}" ]]; then - # Handle spaces in properties values - # https://stackoverflow.com/a/48513046/35577 - values=$(cat "${PUBLISH_PROPERTIES}") - while read -rd $'' line - do - echo "exporting ${line}" - export "${line?}" - done < <(jq -r <<<"$values" 'to_entries|map("\(.key)=\(.value)\u0000")[]') - fi - - PUBLISH_JSON="{}" - APP_BUILDER_VERSION="$(${APP_BUILDER_SCRIPT_PATH} "-?" | grep Version | cut -d\ -f2)" -} - -notify_scripture_earth_update_json() { - local i_json="$1" - local i_type="$2" - local i_url="$3" - - echo "${i_json}" | jq \ - --arg type "${i_type}" \ - --arg idx "${SCRIPTURE_EARTH_ID}" \ - --arg desc "${SCRIPTURE_EARTH_DESCRIPTION}" \ - --arg url "${i_url}" \ - --arg email "${PROJECT_OWNER_EMAIL}" \ - --arg projectName "${PROJECT_NAME}" \ - --arg username "${PROJECT_OWNER_NAME}" \ - --arg organization "${PROJECT_ORGANIZATION}" \ - --arg project "${PROJECT_URL}" \ - '. + [ { type: $type, idx: $idx, url: $url, email: $email, projectName: $projectName, description: $desc, username: $username, organization: $organization}]' -} - -notify_scripture_earth() { - local notify_json="$1" - # curl does not work! SE.org returns 406 Not Acceptable and can't figure out why. - wget \ - --header "Content-Type: application/json" \ - --header "Accept: application/json" \ - --post-data "${notify_json}" \ - "https://scriptureearth.org/api/add_resource.php?v=1&key=${SCRIPTURE_EARTH_KEY}" - -} - -publish_base_update_json() { - local i_json="$1" - - echo "${i_json}" | jq \ - --arg project_url "${PROJECT_URL}" \ - --arg project_name "${PROJECT_NAME}" \ - --arg product_name "${WORKFLOW_PRODUCT_NAME}" \ - --arg project_language "${PROJECT_LANGUAGE}" \ - --arg project_repo "${PROJECT_S3}" \ - --arg publish_url "${PUBLISH_URL}" \ - --arg permalink_url "${PERMALINK_URL}" \ - --arg size "${PUBLISH_SIZE}" \ - --arg app_builder "${APP_BUILDER_SCRIPT_PATH}" \ - --arg app_builder_version "${APP_BUILDER_VERSION}" \ - '. + { project_url: $project_url, project_name: $project_name, product_name: $product_name, project_language: $project_language, project_repo: $project_repo, publish_url: $publish_url, permalink_url: $permalink_url, size: $size, app_builder: $app_builder, app_builder_version: $app_builder_version }' -} - -is_production() { - # Return success if SERVER_URL contains the production domain - if [[ -n "${SERVER_URL}" && "${SERVER_URL}" == *"app.scriptoria.io"* ]]; then - return 0 - fi - return 1 -} - -post_publish() { - PUBLISH_JSON="$(publish_base_update_json "${PUBLISH_JSON}")" - - # The WORKFLOW_PRODUCT_NAME is a user generated name and we are expecting the name to - # contain the keywords: ios, android, google (play), and pwa. So the system admin - # should be aware when adding new products. - WORKFLOW_PRODUCT_NAME_LOWER=$(echo "${WORKFLOW_PRODUCT_NAME}" | awk '{print tolower($0)}') - if is_production && [[ "${PUBLISH_NOTIFY}" != "" ]]; then - # See S1: Services/BuildEngine: BuildEngineServiceBase::AddProductProperitiesToEnvironment - # See S2: node-server/job-executors/common.build-publish.ts: addProductPropertiesToEnvironment - # for the list of properties that are added. - - for NOTIFY_SERVER in ${PUBLISH_NOTIFY/,/ } - do - # Notify Scripture Earth - if [[ "${NOTIFY_SERVER})" == *"SCRIPTURE_EARTH"* && "${SCRIPTURE_EARTH_KEY}" != "" ]]; then - EMPTY_NOTIFY_JSON="[]" - NOTIFY_JSON=$EMPTY_NOTIFY_JSON - if [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"ios"* ]]; then - # Send Notification for iOS Asset Package - NOTIFY_TYPE="ios" - # change protocol to "asset://" so that container app will recognize as asset-package - # shellcheck disable=SC2001 - NOTIFY_URL="$(sed -e 's/https*:/asset:/' <<< "$SERVER_URL")/api/products/${PRODUCT_ID}/files/published/asset-package" - NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" - elif [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"android"* ]]; then - # Send Notification for Android app to Google Play - NOTIFY_TYPE="apk" - NOTIFY_URL="${SERVER_URL}/api/products/${PRODUCT_ID}/files/published/apk" - NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" - if [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"google"* ]]; then - # Send additional notification for Android app to Google Play - NOTIFY_TYPE="google_play" - NOTIFY_URL="${PUBLISH_URL}" - NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" - fi - elif [[ $WORKFLOW_PRODUCT_NAME_LOWER == *"pwa"* ]]; then - # Send Notification for PWA - NOTIFY_TYPE="sab_html" - NOTIFY_URL="${PUBLISH_URL}" - NOTIFY_JSON="$(notify_scripture_earth_update_json "${NOTIFY_JSON}" "${NOTIFY_TYPE}" "${NOTIFY_URL}")" - fi - - if [[ "${NOTIFY_JSON}" != "{$EMPTY_NOTIFY_JSON}" ]]; then - notify_scripture_earth "${NOTIFY_JSON}" - fi - else - if [[ -f "${OUTPUT_DIR}/asset-notify.json" ]]; then - echo "NOTIFY: ${NOTIFY_SERVER}" - SECRETS_SUBDIR="notify/${NOTIFY_SERVER}" - sync_secrets "${SECRETS_SUBDIR}" - ENDPOINT="${SECRETS_DIR}/endpoint.json" - echo "ENDPOINT: ${ENDPOINT}" - find "${SECRETS_DIR}" - if [[ -f "${ENDPOINT}" ]]; then - cat "${ENDPOINT}" - NOTIFY_URL=$(jq -r '.url' "${ENDPOINT}") - NOTIFY_ARGS_FILE=$(mktemp) - # How to loop json array in bash - # https://www.starkandwayne.com/blog/bash-for-loop-over-json-array-using-jq/ - NOTIFY_HEADERS=$(jq -r '.headers[] | @base64' "${ENDPOINT}") - for header in $NOTIFY_HEADERS - do - echo "header = \"$(echo "$header" | base64 --decode)\"" >> "${NOTIFY_ARGS_FILE}" - done - echo "header = \"Accept: application/json\"" >> "${NOTIFY_ARGS_FILE}" - echo "header = \"Content-Type: application/json\"" >> "${NOTIFY_ARGS_FILE}" - NOTIFY_JSON=$(cat "${OUTPUT_DIR}/asset-notify.json") - curl -X POST --config "${NOTIFY_ARGS_FILE}" --data "${NOTIFY_JSON}" "${NOTIFY_URL}" - fi - fi - fi - done - fi - - echo "${PUBLISH_JSON}" > "${OUTPUT_DIR}/publish.json" -} - -APK_FILES=( "${ARTIFACTS_DIR}"/*.apk ) -AAB_FILES=( "${ARTIFACTS_DIR}"/*.aab ) - -prepare_publish -env | sort -echo "TARGETS: $TARGETS" -for target in $TARGETS -do - case "$target" in - "google-play") publish_google_play ;; - "rclone") publish_rclone ;; - "s3-bucket") publish_s3_bucket ;; - *) publish_gradle "$target" ;; - esac -done - -post_publish diff --git a/application/console/views/cron/scripts/utilities/Helper.groovy b/application/console/views/cron/scripts/utilities/Helper.groovy deleted file mode 100644 index 1a6ad198..00000000 --- a/application/console/views/cron/scripts/utilities/Helper.groovy +++ /dev/null @@ -1,19 +0,0 @@ -package utilities; - -import groovy.text.*; - -public class Helper { - - /* - * Prepare the script by replacing groovy-variables (marked with @@{}) - * while still allowing Jenkins variables (marked with $). - */ - def static prepareScript(String script, Map binding) { - def templateEngine = new SimpleTemplateEngine(); - def template = templateEngine.createTemplate( - script.replaceAll(~/\$/, /\\\$/).replaceAll(~/@@/, /\$/)); - - return template.make(binding).toString(); - } -} - diff --git a/application/environments/dev/backend/config/main-local.php b/application/environments/dev/backend/config/main-local.php deleted file mode 100644 index d9e38094..00000000 --- a/application/environments/dev/backend/config/main-local.php +++ /dev/null @@ -1,21 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; - -if (!YII_ENV_TEST) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = 'yii\debug\Module'; - - $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; -} - -return $config; diff --git a/application/environments/dev/backend/config/params-local.php b/application/environments/dev/backend/config/params-local.php deleted file mode 100644 index d0b9c34f..00000000 --- a/application/environments/dev/backend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/application/environments/dev/backend/web/index.php b/application/environments/dev/backend/web/index.php deleted file mode 100644 index 60381674..00000000 --- a/application/environments/dev/backend/web/index.php +++ /dev/null @@ -1,18 +0,0 @@ -run(); diff --git a/application/environments/dev/common/config/main-local.php b/application/environments/dev/common/config/main-local.php deleted file mode 100644 index 43db30ea..00000000 --- a/application/environments/dev/common/config/main-local.php +++ /dev/null @@ -1,20 +0,0 @@ - [ - 'db' => [ - 'class' => 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', - 'username' => 'root', - 'password' => '', - 'charset' => 'utf8', - ], - 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', - 'viewPath' => '@common/mail', - // send all mails to a file by default. You have to set - // 'useFileTransport' to false and configure a transport - // for the mailer to send real emails. - 'useFileTransport' => true, - ], - ], -]; diff --git a/application/environments/dev/common/config/params-local.php b/application/environments/dev/common/config/params-local.php deleted file mode 100644 index d0b9c34f..00000000 --- a/application/environments/dev/common/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; - -if (!YII_ENV_TEST) { - // configuration adjustments for 'dev' environment - $config['bootstrap'][] = 'debug'; - $config['modules']['debug'] = 'yii\debug\Module'; - - $config['bootstrap'][] = 'gii'; - $config['modules']['gii'] = 'yii\gii\Module'; -} - -return $config; diff --git a/application/environments/dev/frontend/config/params-local.php b/application/environments/dev/frontend/config/params-local.php deleted file mode 100644 index d0b9c34f..00000000 --- a/application/environments/dev/frontend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/application/environments/dev/frontend/web/index.php b/application/environments/dev/frontend/web/index.php deleted file mode 100644 index 60381674..00000000 --- a/application/environments/dev/frontend/web/index.php +++ /dev/null @@ -1,18 +0,0 @@ -run(); diff --git a/application/environments/dev/yii b/application/environments/dev/yii deleted file mode 100644 index 8cc58275..00000000 --- a/application/environments/dev/yii +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env php -run(); -exit($exitCode); diff --git a/application/environments/index.php b/application/environments/index.php deleted file mode 100644 index 5bb0acff..00000000 --- a/application/environments/index.php +++ /dev/null @@ -1,61 +0,0 @@ - [ - * 'path' => 'directory storing the local files', - * 'setWritable' => [ - * // list of directories that should be set writable - * ], - * 'setExecutable' => [ - * // list of directories that should be set executable - * ], - * 'setCookieValidationKey' => [ - * // list of config files that need to be inserted with automatically generated cookie validation keys - * ], - * 'createSymlink' => [ - * // list of symlinks to be created. Keys are symlinks, and values are the targets. - * ], - * ], - * ]; - * ``` - */ -return [ - 'Development' => [ - 'path' => 'dev', - 'setWritable' => [ - 'backend/runtime', - 'backend/web/assets', - 'frontend/runtime', - 'frontend/web/assets', - ], - 'setExecutable' => [ - 'yii', - ], - 'setCookieValidationKey' => [ - 'backend/config/main-local.php', - 'frontend/config/main-local.php', - ], - ], - 'Production' => [ - 'path' => 'prod', - 'setWritable' => [ - 'backend/runtime', - 'backend/web/assets', - 'frontend/runtime', - 'frontend/web/assets', - ], - 'setExecutable' => [ - 'yii', - ], - 'setCookieValidationKey' => [ - 'backend/config/main-local.php', - 'frontend/config/main-local.php', - ], - ], -]; diff --git a/application/environments/prod/backend/config/main-local.php b/application/environments/prod/backend/config/main-local.php deleted file mode 100644 index af46ba33..00000000 --- a/application/environments/prod/backend/config/main-local.php +++ /dev/null @@ -1,9 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; diff --git a/application/environments/prod/backend/config/params-local.php b/application/environments/prod/backend/config/params-local.php deleted file mode 100644 index d0b9c34f..00000000 --- a/application/environments/prod/backend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/application/environments/prod/common/config/main-local.php b/application/environments/prod/common/config/main-local.php deleted file mode 100644 index 84c4d9f1..00000000 --- a/application/environments/prod/common/config/main-local.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'db' => [ - 'class' => 'yii\db\Connection', - 'dsn' => 'mysql:host=localhost;dbname=yii2advanced', - 'username' => 'root', - 'password' => '', - 'charset' => 'utf8', - ], - 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', - 'viewPath' => '@common/mail', - ], - ], -]; diff --git a/application/environments/prod/common/config/params-local.php b/application/environments/prod/common/config/params-local.php deleted file mode 100644 index d0b9c34f..00000000 --- a/application/environments/prod/common/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ - [ - 'request' => [ - // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation - 'cookieValidationKey' => '', - ], - ], -]; diff --git a/application/environments/prod/frontend/config/params-local.php b/application/environments/prod/frontend/config/params-local.php deleted file mode 100644 index d0b9c34f..00000000 --- a/application/environments/prod/frontend/config/params-local.php +++ /dev/null @@ -1,3 +0,0 @@ -run(); diff --git a/application/environments/prod/yii b/application/environments/prod/yii deleted file mode 100644 index c8b6f3fe..00000000 --- a/application/environments/prod/yii +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env php -run(); -exit($exitCode); diff --git a/application/frontend/assets/AppAsset.php b/application/frontend/assets/AppAsset.php deleted file mode 100644 index 995e3dc3..00000000 --- a/application/frontend/assets/AppAsset.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @since 2.0 - */ -class AppAsset extends AssetBundle -{ - public $basePath = '@webroot'; - public $baseUrl = '@web'; - public $css = [ - 'css/site.css', - ]; - public $js = [ - ]; - public $depends = [ - 'yii\web\YiiAsset', - 'yii\bootstrap\BootstrapAsset', - ]; -} diff --git a/application/frontend/components/JobControllerUtils.php b/application/frontend/components/JobControllerUtils.php deleted file mode 100644 index a1c40357..00000000 --- a/application/frontend/components/JobControllerUtils.php +++ /dev/null @@ -1,124 +0,0 @@ -validateJob($id); - $runningRelease = Release::findOne([ - 'build_id' => $build_id, - 'status' => ["active", "accepted"] - ]); - if($runningRelease){ - throw new ServerErrorHttpException("Release $runningRelease->id already in progress for build $build_id"); - } - $build = Build::findOneById($id, $build_id); - if (!$build){ - throw new NotFoundHttpException("Job $id Build $build_id not found"); - } - $version_code = $build->version_code; - - $verify_result = $this->verifyChannel($id, $channel, $version_code); - $environmentString = json_encode($environment); - $release = $build->createRelease($channel, $targets, $environmentString); - $release->title = $title; - $release->defaultLanguage = $defaultLanguage; - $release->promote_from = $verify_result; - $release->save(); - - return $release; - } - /** - * @param $id - * @param $build_id - * @param $release_id - * @return null|static - * @throws NotFoundHttpException - */ - public function lookupRelease($id, $build_id, $release_id) - { - // Do we need to verify that the job id, build id are correct??? - $build = Build::findOne(['id' => $build_id, 'job_id' => $id]); - if (!$build) { - throw new NotFoundHttpException(); - } - - $release = Release::findOne(['id' => $release_id, 'build_id' => $build_id]); - if (!$release) { - throw new NotFoundHttpException(); - } - return $release; - } - /** - * @param $id - The job id - * @param $channel - The channel the build is being released to - * @param $version_code - The version currently being released - * @throws ServerErrorHttpException - * $return null if not already released to another channel - * else return channel to promote from - */ - public function verifyChannel($id, $channel, $version_code) - { - $retval = null; - $last_build = null; - $highestPublishedChannel = Build::CHANNEL_UNPUBLISHED; - foreach (Build::find()->where([ - 'job_id' => $id, - 'status' => Build::STATUS_COMPLETED, - 'result' => "SUCCESS"])->each(50) as $build){ - $last_build = $build; - if ($build->channel && ($build->version_code) - && ($build->channel != Build::CHANNEL_UNPUBLISHED) - && ($version_code == $build->version_code)) { - $highestPublishedChannel = $this->getHighestPublishedChannel($highestPublishedChannel, $build); - } - } - if (($highestPublishedChannel != $channel) - && ($highestPublishedChannel != Build::CHANNEL_UNPUBLISHED)) { - if ($build->isValidChannelTransition($channel, $highestPublishedChannel)) { - $retval = $highestPublishedChannel; - } else { - throw new ServerErrorHttpException("Job $id already released under channel $build->channel for version $version_code", 1453326645); - } - } - return $retval; - } - public function validateJob($id) - { - if (!$this->viewJob($id)) { - throw new NotFoundHttpException("Job $id not found"); - } - } - private function getHighestPublishedChannel($old, $build) - { - $retVal = $old; - if ( ! $build->isValidChannelTransition($old)) { - $retVal = $build->channel; - } - return $retVal; - } -} diff --git a/application/frontend/config/.gitignore b/application/frontend/config/.gitignore deleted file mode 100644 index 20da318c..00000000 --- a/application/frontend/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main-local.php -params-local.php \ No newline at end of file diff --git a/application/frontend/config/bootstrap.php b/application/frontend/config/bootstrap.php deleted file mode 100644 index b3d9bbc7..00000000 --- a/application/frontend/config/bootstrap.php +++ /dev/null @@ -1 +0,0 @@ - 'app-frontend', - 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log'], - 'controllerNamespace' => 'frontend\controllers', - 'aliases' => [ - "@bower" => "@vendor/bower-asset", - "@npm" => "@vendor/npm-asset" - ], - 'components' => [ - 'user' => [ - 'identityClass' => 'common\models\User', - 'enableAutoLogin' => true, - ], - 'log' => [ - 'traceLevel' => YII_DEBUG ? 3 : 0, - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], - ], - 'errorHandler' => [ - 'errorAction' => 'site/error', - ], - 'request' => [ - 'enableCookieValidation' => true, - 'enableCsrfValidation' => true, - 'cookieValidationKey' => $FRONT_COOKIE_KEY, - 'parsers' => [ - 'application/json' => 'yii\web\JsonParser' - ] - ], - 'urlManager' => [ - 'enablePrettyUrl' => true, - 'enableStrictParsing' => false, - 'showScriptName' => false, - 'baseUrl' => $BASE_URL, - 'rules' => [ - [ - 'class' => 'yii\rest\UrlRule', - 'controller' => 'job', - 'pluralize' => false, - 'extraPatterns' => [ - 'GET' => 'index-jobs', - 'GET ' => 'view-job', - 'DELETE ' => 'delete-job', - 'GET /build' => 'index-builds', - 'GET /build/' => 'view-build', - 'DELETE /build/' => 'delete-build', - 'PUT /build/' => 'publish-build', - 'POST /build' => 'new-build', - 'GET /build//release/' => 'view-release', - 'DELETE /build//release/' => 'delete-release', - ] - ], - [ - 'class' => 'yii\rest\UrlRule', - 'controller' => 'project', - 'pluralize' => false, - 'extraPatterns' => [ // It doesn't look like these are needed, since they match the action names - 'POST' => 'new-project', - 'GET' => 'index-projects', - 'GET ' => 'view-project', - 'DELETE ' => 'delete-project', - 'PUT ' => 'modify-project', - 'POST /token' => 'create-token' - ], - ], - [ 'class' => 'yii\rest\UrlRule', - 'controller' => 'system', - 'pluralize' => 'false', - ] - ], - ], - ], - 'modules' => [ - - ], - 'params' => [ - 'buildEngineProjectsBucket' => $BUILD_ENGINE_PROJECTS_BUCKET, - 'buildEngineArtifactsBucketRegion' => $BUILD_ENGINE_ARTIFACTS_BUCKET_REGION, - 'codeBuildImageRepo' => $CODE_BUILD_IMAGE_REPO, - 'codeBuildImageTag' => $CODE_BUILD_IMAGE_TAG, - ], -]; diff --git a/application/frontend/controllers/BuildAdminController.php b/application/frontend/controllers/BuildAdminController.php deleted file mode 100644 index 1e25ad8a..00000000 --- a/application/frontend/controllers/BuildAdminController.php +++ /dev/null @@ -1,139 +0,0 @@ - [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all Build models. - * @return mixed - */ - public function actionIndex() - { - $dataProvider = new ActiveDataProvider([ - 'query' => Build::find(), - 'sort' => ['defaultOrder' => ['id'=>SORT_DESC]], - ]); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Displays a single Build model. - * @param integer $id - * @return mixed - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } - - /** - * Creates a new Build model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new Build(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing Build model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Retry copying the results to S3 if - * @param integer $id - * @return mixed - */ - public function actionRetryCopy($id) - { - $model = $this->findModel($id); - - $task = OperationQueue::SAVETOS3; - $build_id = $model->id; - OperationQueue::findOrCreate($task, $build_id, null); - - return $this->redirect(['operation-queue-admin/index']); - } - - /** - * Deletes an existing Build model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the Build model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Build the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Build::findOne($id)) !== null) { - return $model; - } else { - throw new NotFoundHttpException('The requested page does not exist.'); - } - } -} diff --git a/application/frontend/controllers/ClientAdminController.php b/application/frontend/controllers/ClientAdminController.php deleted file mode 100644 index 3dd0b202..00000000 --- a/application/frontend/controllers/ClientAdminController.php +++ /dev/null @@ -1,122 +0,0 @@ - [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all Client models. - * @return mixed - */ - public function actionIndex() - { - $dataProvider = new ActiveDataProvider([ - 'query' => Client::find(), - 'sort' => ['defaultOrder' => ['id'=>SORT_DESC]], - ]); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Displays a single Client model. - * @param integer $id - * @return mixed - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } - - /** - * Creates a new Client model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new Client(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing Client model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Deletes an existing Client model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the Client model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Client the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Client::findOne($id)) !== null) { - return $model; - } else { - throw new NotFoundHttpException('The requested page does not exist.'); - } - } -} diff --git a/application/frontend/controllers/JobAdminController.php b/application/frontend/controllers/JobAdminController.php deleted file mode 100644 index 9b10fd25..00000000 --- a/application/frontend/controllers/JobAdminController.php +++ /dev/null @@ -1,122 +0,0 @@ - [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all Job models. - * @return mixed - */ - public function actionIndex() - { - $dataProvider = new ActiveDataProvider([ - 'query' => Job::find(), - 'sort' => ['defaultOrder' => ['id'=>SORT_DESC]], - ]); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Displays a single Job model. - * @param integer $id - * @return mixed - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } - - /** - * Creates a new Job model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new Job(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing Job model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Deletes an existing Job model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the Job model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Job the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Job::findOne($id)) !== null) { - return $model; - } else { - throw new NotFoundHttpException('The requested page does not exist.'); - } - } -} diff --git a/application/frontend/controllers/JobController.php b/application/frontend/controllers/JobController.php deleted file mode 100644 index 48e4c473..00000000 --- a/application/frontend/controllers/JobController.php +++ /dev/null @@ -1,142 +0,0 @@ -jcUtils = new JobControllerUtils; - parent::__construct($id, $module, $config); - } - public $modelClass = 'common\models\Job'; - - public function actionIndexBuilds($id) { - $this->jcUtils->validateJob($id); - $builds = Build::findAllActiveByJobId($id); - if (!$builds){ - throw new NotFoundHttpException(); - } - return $builds; - } - - public function actionIndexJobs() { - $clientId = Job::getCurrentClientId(); - $jobs = Job::findAllByClientId($clientId); - if (!$jobs) { - throw new NotFoundHttpException(); - } - return $jobs; - } - public function actionViewJob($id) { - return $this->jcUtils->viewJob($id); - } - public function actionDeleteJob($id) { - $job = Job::findByIdFiltered($id); - if (!$job) { - throw new NotFoundHttpException("Job $id not found"); - } - $job->delete(); - } - public function actionViewBuild($id, $build_id) { - $this->jcUtils->validateJob($id); - $build = Build::findOneById($id, $build_id); - if (!$build){ - throw new NotFoundHttpException("Job $id Build $build_id not found"); - } - return $build; - } - - public function actionNewBuild($id) { - $job = Job::findByIdFiltered($id); - if (!$job){ - throw new NotFoundHttpException("Job $id not found", 1443810472); - } - $targets = \Yii::$app->request->getBodyParam('targets', null); - $environment = \Yii::$app->request->getBodyParam('environment', null); - $environmentString = json_encode($environment); - $build = $job->createBuild($targets, $environmentString); - if (!$build){ - throw new ServerErrorHttpException("Could not create Build for Job $id", 1443810508); - } - return $build; - } - public function actionDeleteBuild($id, $build_id) { - $this->jcUtils->validateJob($id); - $build = Build::findOneById($id, $build_id); - if (!$build){ - throw new NotFoundHttpException("Job $id Build $build_id not found"); - } - $build->delete(); - } - public function actionPublishBuild($id, $build_id) { - $channel = \Yii::$app->request->getBodyParam('channel', null); - $title = \Yii::$app->request->getBodyParam('title', null); - $defaultLanguage = \Yii::$app->request->getBodyParam('defaultLanguage', null); - $targets = \Yii::$app->request->getBodyParam('targets', null); - $environment = \Yii::$app->request->getBodyParam('environment', null); - $release = $this->jcUtils->publishBuild($id, $build_id, $channel, $title, $defaultLanguage, $targets, $environment); - return $release; - } - - public function actionViewRelease($id, $build_id, $release_id) { - $this->jcUtils->validateJob($id); - $release = $this->jcUtils->lookupRelease($id, $build_id, $release_id); - return $release; - } - - public function actionDeleteRelease($id, $build_id, $release_id) { - $this->jcUtils->validateJob($id); - $release = $this->jcUtils->lookupRelease($id, $build_id, $release_id); - $release->delete(); - } - - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(), [ - 'authenticator' => [ - 'class' => CompositeAuth::className(), - 'authMethods' => [ - HttpBearerAuth::className(), // Use header ... Authorization: Bearer abc123 - ], - ], - 'access' => [ - 'class' => AccessControl::className(), - 'rules' => [ - [ - 'allow' => true, - 'roles' => ['@'], // Any logged in user - ], - ], - 'denyCallback' => function($rule, $action){ - if(\Yii::$app->user->isGuest){ - throw new UnauthorizedHttpException(); - } else { - throw new ForbiddenHttpException(); - } - }, - ] - ]); - } -} diff --git a/application/frontend/controllers/OperationQueueAdminController.php b/application/frontend/controllers/OperationQueueAdminController.php deleted file mode 100644 index 924f7d5a..00000000 --- a/application/frontend/controllers/OperationQueueAdminController.php +++ /dev/null @@ -1,122 +0,0 @@ - [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all OperationQueue models. - * @return mixed - */ - public function actionIndex() - { - $dataProvider = new ActiveDataProvider([ - 'query' => OperationQueue::find(), - 'sort' => ['defaultOrder' => ['id'=>SORT_DESC]], - ]); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Displays a single OperationQueue model. - * @param integer $id - * @return mixed - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } - - /** - * Creates a new OperationQueue model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new OperationQueue(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing OperationQueue model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Deletes an existing OperationQueue model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the OperationQueue model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return OperationQueue the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = OperationQueue::findOne($id)) !== null) { - return $model; - } else { - throw new NotFoundHttpException('The requested page does not exist.'); - } - } -} diff --git a/application/frontend/controllers/ProjectAdminController.php b/application/frontend/controllers/ProjectAdminController.php deleted file mode 100644 index 250732ff..00000000 --- a/application/frontend/controllers/ProjectAdminController.php +++ /dev/null @@ -1,123 +0,0 @@ - [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all Project models. - * @return mixed - */ - public function actionIndex() - { - $dataProvider = new ActiveDataProvider([ - 'query' => Project::find(), - 'sort' => ['defaultOrder' => ['id'=>SORT_DESC]], - ]); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Displays a single Project model. - * @param integer $id - * @return mixed - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } - - /** - * Creates a new Project model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new Project(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing Project model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Deletes an existing Project model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the Project model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Client the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Project::findOne($id)) !== null) { - return $model; - } else { - throw new NotFoundHttpException('The requested page does not exist.'); - } - } -} - diff --git a/application/frontend/controllers/ProjectController.php b/application/frontend/controllers/ProjectController.php deleted file mode 100644 index 525f0279..00000000 --- a/application/frontend/controllers/ProjectController.php +++ /dev/null @@ -1,136 +0,0 @@ -sts = new STS(); - } - public $modelClass = 'common\models\Project'; - - public function actionNewProject() { - $storage_type = \Yii::$app->request->getBodyParam('storage_type', null); - $project = new Project(); - $project->load(\Yii::$app->request->post(), ''); - - if ($storage_type === "s3") { - // Need to save to initialize id field. Need to make sure that - // the status is marked complete so that manage projects won't - // happen to catch it here and try to create codecreate repository - $project->status = Project::STATUS_COMPLETED; // TODO: Remove when git is no longer supported - $project->save(); - $project->setS3Project(); - } - $project->save(); - return $project; - } - public function actionIndexProjects() { - $clientId = Project::getCurrentClientId(); - $projects = Project::findAllByClientId($clientId); - if (!$projects) { - throw new NotFoundHttpException(); - } - return $projects; - } - public function actionViewProject($id) { - $project = Project::findByIdFiltered($id); - if (!$project) { - throw new NotFoundHttpException("Project $id not found"); - } - return $project; - } - public function actionDeleteProject($id) { - $project = Project::findByIdFiltered($id); - if (!$project) { - throw new NotFoundHttpException("Project $id not found"); - } - $project->status = Project::STATUS_DELETE_PENDING; - $project->save(); - } - public function actionModifyProject($id) { - $project = Project::findByIdFiltered($id); - if (!$project) { - throw new NotFoundHttpException("Project $id not found"); - } - $publishing_key = \Yii::$app->request->getBodyParam('publishing_key', null); - $user_id = \Yii::$app->request->getBodyParam('user_id', null); - $url = $project->url; - if (($publishing_key == null) || ($user_id == null)) - { - throw new BadRequestHttpException("Publishing key or user id not set"); - } - if ($url == null) - { - throw new BadRequestHttpException("Attempting to modify project with no url"); - } - // Start modify operation - $task = OperationQueue::UPDATEPROJECT; - $project_id = $project->id; - $parms = $publishing_key . ',' . $user_id; - OperationQueue::findOrCreate($task, $project_id, $parms); - return $project; - } - public function actionCreateToken($id) { - $name = \Yii::$app->request->getBodyParam('name', null); - $readOnly = \Yii::$app->request->getBodyParam('read_only', false); - - $project = Project::findByIdFiltered($id); - if (!$project->isS3Project()) { - throw new BadRequestHttpException("Attempting to get token for wrong project type"); - } - - return $this->sts->getProjectAccessToken($project, $name, $readOnly); - } - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(), [ - 'authenticator' => [ - 'class' => CompositeAuth::className(), - 'authMethods' => [ - HttpBearerAuth::className(), // Use header ... Authorization: Bearer abc123 - ], - ], - 'access' => [ - 'class' => AccessControl::className(), - 'rules' => [ - [ - 'allow' => true, - 'roles' => ['@'], // Any logged in user - ], - ], - 'denyCallback' => function($rule, $action){ - if(\Yii::$app->user->isGuest){ - throw new UnauthorizedHttpException(); - } else { - throw new ForbiddenHttpException(); - } - }, - ] - ]); - } -} - diff --git a/application/frontend/controllers/ReleaseAdminController.php b/application/frontend/controllers/ReleaseAdminController.php deleted file mode 100644 index a0184251..00000000 --- a/application/frontend/controllers/ReleaseAdminController.php +++ /dev/null @@ -1,122 +0,0 @@ - [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'delete' => ['post'], - ], - ], - ]; - } - - /** - * Lists all Release models. - * @return mixed - */ - public function actionIndex() - { - $dataProvider = new ActiveDataProvider([ - 'query' => Release::find(), - 'sort' => ['defaultOrder' => ['id'=>SORT_DESC]], - ]); - - return $this->render('index', [ - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Displays a single Release model. - * @param integer $id - * @return mixed - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } - - /** - * Creates a new Release model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new Release(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('create', [ - 'model' => $model, - ]); - } - } - - /** - * Updates an existing Release model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); - } - } - - /** - * Deletes an existing Release model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the Release model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Release the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Release::findOne($id)) !== null) { - return $model; - } else { - throw new NotFoundHttpException('The requested page does not exist.'); - } - } -} diff --git a/application/frontend/controllers/SiteController.php b/application/frontend/controllers/SiteController.php deleted file mode 100644 index c160a456..00000000 --- a/application/frontend/controllers/SiteController.php +++ /dev/null @@ -1,171 +0,0 @@ - [ - 'class' => AccessControl::className(), - 'only' => ['logout', 'signup'], - 'rules' => [ - [ - 'actions' => ['signup'], - 'allow' => true, - 'roles' => ['?'], - ], - [ - 'actions' => ['logout'], - 'allow' => true, - 'roles' => ['@'], - ], - ], - ], - 'verbs' => [ - 'class' => VerbFilter::className(), - 'actions' => [ - 'logout' => ['post'], - ], - ], - ]; - } - - /** - * @inheritdoc - */ - public function actions() - { - return [ - 'error' => [ - 'class' => 'yii\web\ErrorAction', - ], - 'captcha' => [ - 'class' => 'yii\captcha\CaptchaAction', - 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, - ], - ]; - } - - public function actionIndex() - { - return $this->render('index'); - } - - public function actionLogin() - { - if (!\Yii::$app->user->isGuest) { - return $this->goHome(); - } - - $model = new LoginForm(); - if ($model->load(Yii::$app->request->post()) && $model->login()) { - return $this->goBack(); - } else { - return $this->render('login', [ - 'model' => $model, - ]); - } - } - - public function actionLogout() - { - Yii::$app->user->logout(); - - return $this->goHome(); - } - - public function actionContact() - { - $model = new ContactForm(); - if ($model->load(Yii::$app->request->post()) && $model->validate()) { - if ($model->sendEmail(Yii::$app->params['adminEmail'])) { - Yii::$app->session->setFlash('success', 'Thank you for contacting us. We will respond to you as soon as possible.'); - } else { - Yii::$app->session->setFlash('error', 'There was an error sending email.'); - } - - return $this->refresh(); - } else { - return $this->render('contact', [ - 'model' => $model, - ]); - } - } - - public function actionAbout() - { - return $this->render('about'); - } - - public function actionSignup() - { - $model = new SignupForm(); - if ($model->load(Yii::$app->request->post())) { - if ($user = $model->signup()) { - if (Yii::$app->getUser()->login($user)) { - return $this->goHome(); - } - } - } - - return $this->render('signup', [ - 'model' => $model, - ]); - } - - public function actionRequestPasswordReset() - { - $model = new PasswordResetRequestForm(); - if ($model->load(Yii::$app->request->post()) && $model->validate()) { - if ($model->sendEmail()) { - Yii::$app->getSession()->setFlash('success', 'Check your email for further instructions.'); - - return $this->goHome(); - } else { - Yii::$app->getSession()->setFlash('error', 'Sorry, we are unable to reset password for email provided.'); - } - } - - return $this->render('requestPasswordResetToken', [ - 'model' => $model, - ]); - } - - public function actionResetPassword($token) - { - try { - $model = new ResetPasswordForm($token); - } catch (InvalidParamException $e) { - throw new BadRequestHttpException($e->getMessage()); - } - - if ($model->load(Yii::$app->request->post()) && $model->validate() && $model->resetPassword()) { - Yii::$app->getSession()->setFlash('success', 'New password was saved.'); - - return $this->goHome(); - } - - return $this->render('resetPassword', [ - 'model' => $model, - ]); - } -} diff --git a/application/frontend/controllers/SystemController.php b/application/frontend/controllers/SystemController.php deleted file mode 100644 index f3879a4a..00000000 --- a/application/frontend/controllers/SystemController.php +++ /dev/null @@ -1,354 +0,0 @@ -all(); - } catch (\Exception $e) { - throw new ServerErrorHttpException("Unable to connect to db, error code " . $e->getCode(), $e->getCode()); - } - - // Prepare default response structure - $versions = []; - $imageHash = null; - - // Prefer params from frontend config/main.php (no getenv fallback) - $repoConfig = Yii::$app->params['codeBuildImageRepo'] ?? null; - $tagFilter = Yii::$app->params['codeBuildImageTag'] ?? null; - $region = Yii::$app->params['buildEngineArtifactsBucketRegion'] ?? 'us-east-1'; - - // Status: log repo config presence - Yii::info('SystemController::actionCheck - repoConfig=' . ($repoConfig ?: '(none)')); - - // Log a warning if a repo is configured but the AWS ECR client class isn't available - if ($repoConfig && !class_exists('\\Aws\\Ecr\\EcrClient')) { - Yii::warning('ECR client class "\\Aws\\Ecr\\EcrClient" not found. Install the AWS SDK for PHP (composer require aws/aws-sdk-php) so versions can be discovered from ECR.'); - } - - // Try to query ECR if AWS SDK is available and repo is configured - if ($repoConfig && class_exists('\\Aws\\Ecr\\EcrClient')) { - Yii::info('SystemController::actionCheck - Aws\\Ecr\\EcrClient available, attempting ECR query (region=' . $region . ')'); - try { - $client = new \Aws\Ecr\EcrClient([ - 'version' => '2015-09-21', - 'region' => $region, - ]); - - Yii::info('SystemController::actionCheck - EcrClient constructed'); - - // repositoryName for ECR is typically the last path segment if repo includes a path - $repoName = $repoConfig; - if (strpos($repoName, '/') !== false) { - $parts = explode('/', $repoName); - $repoName = end($parts); - } - - Yii::info('SystemController::actionCheck - resolved repositoryName=' . $repoName); - - // Verify repository exists before calling describeImages - $repoMeta = $this->verifyEcrRepositoryExists($client, $repoName); - if (empty($repoMeta)) { - Yii::warning('SystemController::actionCheck - repository verification failed or repository not found: ' . $repoName . ' - skipping describeImages.'); - $imageDetails = []; - } else { - // Describe tagged images - $params = ['repositoryName' => $repoName, 'filter' => ['tagStatus' => 'TAGGED']]; - Yii::info('SystemController::actionCheck - calling describeImages for ' . $repoName); - $resp = $client->describeImages($params); - $imageDetails = $resp->get('imageDetails') ?: []; - - Yii::info('SystemController::actionCheck - describeImages returned ' . count($imageDetails) . ' imageDetails'); - } - - // Look for version information in image manifests - $appNames = [ - 'scriptureappbuilder', - 'readingappbuilder', - 'dictionaryappbuilder', - 'keyboardappbuilder' - ]; - - foreach ($imageDetails as $img) { - if (empty($img['imageTags'])) { - continue; - } - - Yii::info('SystemController::actionCheck - image: ' . json_encode($img)); - - - // Extract image digest (hash) from the image details - this is available without fetching manifest - $imageDigest = $img['imageDigest'] ?? null; - Yii::info('SystemController::actionCheck - found imageDigest: ' . ($imageDigest ?: 'none') . ' for image with tags: ' . implode(', ', $img['imageTags'])); - - foreach ($img['imageTags'] as $imgTag) { - - // Only process tags that match the tagFilter (if set) - if ($tagFilter && strpos($imgTag, $tagFilter) === false) { - Yii::info('SystemController::actionCheck - skipping tag ' . $imgTag . ' (does not match tagFilter)'); - continue; - } - - // Extract versions for all apps from the image manifest/config - $appVersions = $this->fetchAllAppVersionsFromManifest($client, $repoName, $imgTag, $appNames); - - if (!empty($appVersions)) { - $imageHash = $imageDigest; - - foreach ($appVersions as $app => $version) { - $versions[$app] = $version; - Yii::info('SystemController::actionCheck - manifest-derived version for tag ' . $imgTag . ' => ' . $app . ' ' . $version); - } - } else { - Yii::info('SystemController::actionCheck - no app versions found in manifest for tag ' . $imgTag); - } - } - } - } catch (\Exception $e) { - // If ECR query fails (missing creds/permissions or network), leave versions empty - // Do not throw: the health check should still succeed if DB is OK - Yii::warning('SystemController::actionCheck - ECR query failed: ' . $e->getMessage()); - // Detect AccessDenied and add an extra hint to logs - $msg = $e->getMessage(); - if (stripos($msg, 'AccessDenied') !== false || stripos($msg, 'AccessDeniedException') !== false) { - Yii::warning('SystemController::actionCheck - ECR Access Denied. Ensure IAM principal has ecr:DescribeImages for the repository and correct region/account.'); - } - } - } else { - Yii::info('SystemController::actionCheck - skipping ECR query (no repoConfig or ECR client not available)'); - } - - // Build timestamps - use cached creation date if available, otherwise current time - $created = (new \DateTime('now', new \DateTimeZone('UTC')))->format('c'); - - // Debug logging - Yii::info('SystemController::actionCheck - Final $versions structure: ' . json_encode($versions)); - - $response = [ - 'versions' => (object) $versions, // cast to object to ensure JSON object even when empty - 'created' => $created, - 'updated' => $created, - 'imageHash' => $imageHash, - '_links' => [ - 'self' => [ - 'href' => Yii::$app->request->absoluteUrl, - ], - ], - ]; - - return $response; - } - - /** - * Verify that an ECR repository exists and return its metadata. - * Returns repository array on success, or null on not found / error. - * Logs info/warnings for common AWS error codes. - */ - private function verifyEcrRepositoryExists($client, $repoName) - { - try { - Yii::info('SystemController::verifyEcrRepositoryExists - calling describeRepositories for ' . $repoName); - $resp = $client->describeRepositories([ - 'repositoryNames' => [$repoName], - ]); - $repos = $resp->get('repositories') ?: []; - if (count($repos) > 0) { - Yii::info('SystemController::verifyEcrRepositoryExists - repository found: ' . ($repos[0]['repositoryArn'] ?? $repoName)); - return $repos[0]; - } - Yii::warning('SystemController::verifyEcrRepositoryExists - describeRepositories returned empty for ' . $repoName); - return null; - } catch (AwsException $ae) { - $awsCode = $ae->getAwsErrorCode(); - Yii::warning('SystemController::verifyEcrRepositoryExists - AwsException: ' . $ae->getMessage() . ' awsCode=' . $awsCode); - if ($awsCode === 'RepositoryNotFoundException') { - Yii::warning('SystemController::verifyEcrRepositoryExists - repository not found: ' . $repoName); - return null; - } - if (stripos($ae->getMessage(), 'AccessDenied') !== false || $awsCode === 'AccessDeniedException') { - Yii::warning('SystemController::verifyEcrRepositoryExists - access denied for describeRepositories on ' . $repoName . '. Ensure IAM permissions include ecr:DescribeRepositories and ecr:DescribeImages.'); - return null; - } - // Other AWS error: log and return null - Yii::warning('SystemController::verifyEcrRepositoryExists - unexpected ECR error: ' . $ae->getMessage()); - return null; - } catch (\Exception $e) { - Yii::warning('SystemController::verifyEcrRepositoryExists - unexpected error: ' . $e->getMessage()); - return null; - } - } - - /** - * Fetch the image manifest/config and extract version information for all apps. - * Returns an associative array of app names to version strings. - * Does NOT manage caching - that's the caller's responsibility. - */ - private function fetchAllAppVersionsFromManifest($client, $repoName, $imageTag, $appNames) - { - try { - Yii::info('SystemController::fetchAllAppVersionsFromManifest - batchGetImage for ' . $imageTag); - $resp = $client->batchGetImage([ - 'repositoryName' => $repoName, - 'imageIds' => [['imageTag' => $imageTag]], - 'acceptedMediaTypes' => [ - 'application/vnd.docker.distribution.manifest.v2+json', - 'application/vnd.oci.image.manifest.v1+json', - ], - ]); - - $images = $resp->get('images') ?: []; - if (empty($images)) { - Yii::info('SystemController::fetchAllAppVersionsFromManifest - no images returned for tag ' . $imageTag); - return []; - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - received ' . count($images) . ' image(s) for tag ' . $imageTag); - - $imageManifest = $images[0]['imageManifest'] ?? null; - if (!$imageManifest) { - Yii::info('SystemController::fetchAllAppVersionsFromManifest - imageManifest missing for ' . $imageTag); - return []; - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - parsing manifest JSON for ' . $imageTag); - $manifest = json_decode($imageManifest, true); - if (!is_array($manifest)) { - Yii::warning('SystemController::fetchAllAppVersionsFromManifest - unable to decode manifest JSON for ' . $imageTag); - return []; - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - manifest schema version: ' . ($manifest['schemaVersion'] ?? 'unknown') . ' for ' . $imageTag); - - // Locate config digest in manifest (schema v2) to fetch the image config with labels - $configDigest = $manifest['config']['digest'] ?? null; - if (!$configDigest) { - Yii::info('SystemController::fetchAllAppVersionsFromManifest - no config digest found in manifest for ' . $imageTag); - return []; - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - found config digest: ' . $configDigest . ' for ' . $imageTag); - - // Get a download URL for the config blob and fetch it - Yii::info('SystemController::fetchAllAppVersionsFromManifest - getDownloadUrlForLayer for ' . $configDigest); - $dl = $client->getDownloadUrlForLayer([ - 'repositoryName' => $repoName, - 'layerDigest' => $configDigest, - ]); - $url = $dl->get('downloadUrl') ?? null; - if (!$url) { - Yii::warning('SystemController::fetchAllAppVersionsFromManifest - no downloadUrl for config ' . $configDigest); - return []; - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - fetching config from URL for ' . $imageTag); - - // Fetch the config JSON - $configContent = @file_get_contents($url); - if ($configContent === false) { - Yii::warning('SystemController::fetchAllAppVersionsFromManifest - failed to fetch config from ' . $url); - return []; - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - received config content (' . strlen($configContent) . ' bytes) for ' . $imageTag); - - $config = json_decode($configContent, true); - if (!is_array($config)) { - Yii::warning('SystemController::fetchAllAppVersionsFromManifest - unable to decode config JSON for ' . $imageTag); - return []; - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - parsed config JSON successfully for ' . $imageTag); - - // Common places for labels - $labels = []; - if (!empty($config['config']['Labels']) && is_array($config['config']['Labels'])) { - $labels = $config['config']['Labels']; - Yii::info('SystemController::fetchAllAppVersionsFromManifest - found ' . count($labels) . ' labels in config.Labels for ' . $imageTag); - } elseif (!empty($config['container_config']['Labels']) && is_array($config['container_config']['Labels'])) { - $labels = $config['container_config']['Labels']; - Yii::info('SystemController::fetchAllAppVersionsFromManifest - found ' . count($labels) . ' labels in container_config.Labels for ' . $imageTag); - } else { - Yii::info('SystemController::fetchAllAppVersionsFromManifest - no labels found in config for ' . $imageTag); - } - - // Log all label keys for debugging - if (!empty($labels)) { - Yii::info('SystemController::fetchAllAppVersionsFromManifest - available label keys: [' . implode(', ', array_keys($labels)) . '] for ' . $imageTag); - } - - // Extract version for each app from labels - $appVersions = []; - foreach ($appNames as $app) { - $labelKey = 'org.opencontainers.image.version_' . $app; - if (!empty($labels[$labelKey])) { - $version = (string)$labels[$labelKey]; - $appVersions[$app] = $version; - Yii::info('SystemController::fetchAllAppVersionsFromManifest - found version for ' . $app . ': ' . $version . ' (label: ' . $labelKey . ') for ' . $imageTag); - } else { - Yii::info('SystemController::fetchAllAppVersionsFromManifest - no version found for ' . $app . ' (looked for label: ' . $labelKey . ') for ' . $imageTag); - } - } - - Yii::info('SystemController::fetchAllAppVersionsFromManifest - extracted ' . count($appVersions) . ' app versions for ' . $imageTag . ': [' . implode(', ', array_map(function($k, $v) { return "$k=$v"; }, array_keys($appVersions), $appVersions)) . ']'); - - return $appVersions; - } catch (AwsException $ae) { - Yii::warning('SystemController::fetchAllAppVersionsFromManifest - AwsException: ' . $ae->getMessage() . ' awsCode=' . $ae->getAwsErrorCode()); - if ($ae->getAwsErrorCode() === 'AccessDeniedException') { - Yii::warning('SystemController::fetchAllAppVersionsFromManifest - AccessDenied. Ensure ecr:BatchGetImage and ecr:GetDownloadUrlForLayer permissions are present.'); - } - return []; - } catch (\Exception $e) { - Yii::warning('SystemController::fetchAllAppVersionsFromManifest - unexpected error: ' . $e->getMessage()); - return []; - } - } - - - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(), [ - 'authenticator' => [ - 'class' => CompositeAuth::className(), - 'authMethods' => [ - HttpBearerAuth::className(), // Use header ... Authorization: Bearer abc123 - ], - ], - 'access' => [ - 'class' => AccessControl::className(), - 'rules' => [ - [ - 'allow' => true, - 'roles' => ['@'], // Any logged in user - ], - ], - 'denyCallback' => function($rule, $action){ - if(\Yii::$app->user->isGuest){ - throw new UnauthorizedHttpException(); - } else { - throw new ForbiddenHttpException(); - } - }, - ] - ]); - } - -} diff --git a/application/frontend/models/ContactForm.php b/application/frontend/models/ContactForm.php deleted file mode 100644 index ec03147c..00000000 --- a/application/frontend/models/ContactForm.php +++ /dev/null @@ -1,61 +0,0 @@ - 'Verification Code', - ]; - } - - /** - * Sends an email to the specified email address using the information collected by this model. - * - * @param string $email the target email address - * @return boolean whether the email was sent - */ - public function sendEmail($email) - { - $emailBody = "Administration Web Site Contact request from $this->name at $this->email".PHP_EOL.PHP_EOL.$this->body; - return Yii::$app->mailer->compose() - ->setTo($email) - ->setFrom([$this->email => $this->name]) - ->setSubject($this->subject) - ->setTextBody($emailBody) - ->send(); - } -} diff --git a/application/frontend/models/PasswordResetRequestForm.php b/application/frontend/models/PasswordResetRequestForm.php deleted file mode 100644 index 20c68102..00000000 --- a/application/frontend/models/PasswordResetRequestForm.php +++ /dev/null @@ -1,60 +0,0 @@ - 'trim'], - ['email', 'required'], - ['email', 'email'], - ['email', 'exist', - 'targetClass' => '\common\models\User', - 'filter' => ['status' => User::STATUS_ACTIVE], - 'message' => 'There is no user with such email.' - ], - ]; - } - - /** - * Sends an email with a link, for resetting the password. - * - * @return boolean whether the email was send - */ - public function sendEmail() - { - /* @var $user User */ - $user = User::findOne([ - 'status' => User::STATUS_ACTIVE, - 'email' => $this->email, - ]); - - if ($user) { - if (!User::isPasswordResetTokenValid($user->password_reset_token)) { - $user->generatePasswordResetToken(); - } - - if ($user->save()) { - return \Yii::$app->mailer->compose(['html' => 'passwordResetToken-html', 'text' => 'passwordResetToken-text'], ['user' => $user]) - ->setFrom([\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot']) - ->setTo($this->email) - ->setSubject('Password reset for ' . \Yii::$app->name) - ->send(); - } - } - - return false; - } -} diff --git a/application/frontend/models/ResetPasswordForm.php b/application/frontend/models/ResetPasswordForm.php deleted file mode 100644 index 5ed3d2ee..00000000 --- a/application/frontend/models/ResetPasswordForm.php +++ /dev/null @@ -1,65 +0,0 @@ -_user = User::findByPasswordResetToken($token); - if (!$this->_user) { - throw new InvalidParamException('Wrong password reset token.'); - } - parent::__construct($config); - } - - /** - * @inheritdoc - */ - public function rules() - { - return [ - ['password', 'required'], - ['password', 'string', 'min' => 6], - ]; - } - - /** - * Resets password. - * - * @return boolean if password was reset. - */ - public function resetPassword() - { - $user = $this->_user; - $user->password = $this->password; - $user->removePasswordResetToken(); - - return $user->save(); - } -} diff --git a/application/frontend/models/SignupForm.php b/application/frontend/models/SignupForm.php deleted file mode 100644 index c071f83d..00000000 --- a/application/frontend/models/SignupForm.php +++ /dev/null @@ -1,58 +0,0 @@ - 'trim'], - ['username', 'required'], - ['username', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This username has already been taken.'], - ['username', 'string', 'min' => 2, 'max' => 255], - - ['email', 'filter', 'filter' => 'trim'], - ['email', 'required'], - ['email', 'email'], - ['email', 'unique', 'targetClass' => '\common\models\User', 'message' => 'This email address has already been taken.'], - - ['password', 'required'], - ['password', 'string', 'min' => 6], - ]; - } - - /** - * Signs user up. - * - * @return User|null the saved model or null if saving fails - */ - public function signup() - { - if ($this->validate()) { - $user = new User(); - $user->username = $this->username; - $user->email = $this->email; - $user->setPassword($this->password); - $user->generateAuthKey(); - if ($user->save()) { - return $user; - } - } - - return null; - } -} diff --git a/application/frontend/runtime/.gitignore b/application/frontend/runtime/.gitignore deleted file mode 100644 index c96a04f0..00000000 --- a/application/frontend/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/application/frontend/views/build-admin/_form.php b/application/frontend/views/build-admin/_form.php deleted file mode 100644 index 30b2cf1e..00000000 --- a/application/frontend/views/build-admin/_form.php +++ /dev/null @@ -1,47 +0,0 @@ - - -
    - - - - field($model, 'job_id')->textInput() ?> - - field($model, 'status')->textInput(['maxlength' => true]) ?> - - field($model, 'build_guid')->textInput() ?> - - field($model, 'result')->textInput(['maxlength' => true]) ?> - - field($model, 'error')->textInput(['maxlength' => true]) ?> - - field($model, 'artifact_url_base')->textInput(['maxlength' => true]) ?> - - field($model, 'artifact_files')->textInput(['maxlength' => true]) ?> - - field($model, 'created')->textInput() ?> - - field($model, 'updated')->textInput() ?> - - field($model, 'channel')->textInput(['maxlength' => true]) ?> - - field($model, 'version_code')->textInput() ?> - - field($model, 'targets')->textInput() ?> - - field($model, 'environment')->textInput() ?> - -
    - isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
    - - - -
    diff --git a/application/frontend/views/build-admin/create.php b/application/frontend/views/build-admin/create.php deleted file mode 100644 index d703c2da..00000000 --- a/application/frontend/views/build-admin/create.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Create Build'; -$this->params['breadcrumbs'][] = ['label' => 'Builds', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/build-admin/index.php b/application/frontend/views/build-admin/index.php deleted file mode 100644 index 4033059f..00000000 --- a/application/frontend/views/build-admin/index.php +++ /dev/null @@ -1,52 +0,0 @@ -title = 'Builds'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - 'btn btn-success']) ?> -

    - - $dataProvider, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - - 'id', - [ - 'attribute' => 'job_id', - 'format' => 'html', - 'value' => function($data) { - return Html::a("$data->job_id", ['job-admin/view', 'id' => $data->job_id]); - }, - ], - 'status', - 'result', - [ - 'attribute'=>'build_guid', - 'format'=>"html", - 'value' => function($data) { - return Html::a("$data->build_guid", $data->codebuild_url); - } - ], - // 'error', - // 'created', - // 'updated', - // 'channel', - // 'version_code', - - ['class' => 'yii\grid\ActionColumn'], - ], - ]); ?> - -
    diff --git a/application/frontend/views/build-admin/update.php b/application/frontend/views/build-admin/update.php deleted file mode 100644 index 691287f7..00000000 --- a/application/frontend/views/build-admin/update.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Update Build: ' . ' ' . $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Builds', 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; -$this->params['breadcrumbs'][] = 'Update'; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/build-admin/view.php b/application/frontend/views/build-admin/view.php deleted file mode 100644 index 42322695..00000000 --- a/application/frontend/views/build-admin/view.php +++ /dev/null @@ -1,71 +0,0 @@ -title = $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Builds', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; - -$artifacts = $model->artifacts(); -$entries = array(); -foreach ($artifacts as $key => $artifact) { - array_push($entries, Html::a($key, $artifact)); -} -$artifacts_value = join(", ", $entries); - -?> -
    - -

    title) ?>

    - -

    - $model->id], ['class' => 'btn btn-primary']) ?> - status == \common\models\Build::STATUS_POSTPROCESSING) { - echo Html::a('Retry Copy', ['retry-copy', 'id' => $model->id], ['class' => 'btn btn-warning']); - } ?> - $model->id], [ - 'class' => 'btn btn-danger', - 'data' => [ - 'confirm' => 'Are you sure you want to delete this item?', - 'method' => 'post', - ], - ]) ?> -

    - - $model, - 'attributes' => [ - 'id', - [ - 'attribute'=>'job_id', - 'format'=>"html", - 'value' => Html::a($model->job_id, ['job-admin/view', 'id' => $model->job_id]), - ], - 'status', - [ - 'attribute'=>'build_guid', - 'format'=>"html", - 'value' => Html::a($model->build_guid, $model->codebuild_url), - ], - 'result', - 'error:url', - [ - 'attribute' => 'artifacts', - 'format'=>'html', - 'value'=> $artifacts_value - ], - 'created', - 'updated', - 'channel', - 'version_code', - 'targets', - 'environment' - ], - ]) ?> - -
    diff --git a/application/frontend/views/client-admin/_form.php b/application/frontend/views/client-admin/_form.php deleted file mode 100644 index 010b5c89..00000000 --- a/application/frontend/views/client-admin/_form.php +++ /dev/null @@ -1,29 +0,0 @@ - - -
    - - - - field($model, 'access_token')->textInput(['maxlength' => true]) ?> - - field($model, 'prefix')->textInput(['maxlength' => true]) ?> - - isNewRecord ? "" : $form->field($model, 'created')->textInput() ?> - - isNewRecord ? "" : $form->field($model, 'updated')->textInput() ?> - -
    - isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
    - - - -
    diff --git a/application/frontend/views/client-admin/create.php b/application/frontend/views/client-admin/create.php deleted file mode 100644 index 5f67bd59..00000000 --- a/application/frontend/views/client-admin/create.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Create Client'; -$this->params['breadcrumbs'][] = ['label' => 'Clients', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/client-admin/index.php b/application/frontend/views/client-admin/index.php deleted file mode 100644 index 5369924b..00000000 --- a/application/frontend/views/client-admin/index.php +++ /dev/null @@ -1,35 +0,0 @@ -title = 'Clients'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - 'btn btn-success']) ?> -

    - - $dataProvider, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - - 'id', - 'access_token', - 'prefix', - 'created', - 'updated', - - ['class' => 'yii\grid\ActionColumn'], - ], - ]); ?> - -
    diff --git a/application/frontend/views/client-admin/update.php b/application/frontend/views/client-admin/update.php deleted file mode 100644 index 414bef5a..00000000 --- a/application/frontend/views/client-admin/update.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Update Client: ' . ' ' . $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Clients', 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; -$this->params['breadcrumbs'][] = 'Update'; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/client-admin/view.php b/application/frontend/views/client-admin/view.php deleted file mode 100644 index eb709bce..00000000 --- a/application/frontend/views/client-admin/view.php +++ /dev/null @@ -1,39 +0,0 @@ -title = $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Clients', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - $model->id], ['class' => 'btn btn-primary']) ?> - $model->id], [ - 'class' => 'btn btn-danger', - 'data' => [ - 'confirm' => 'Are you sure you want to delete this item?', - 'method' => 'post', - ], - ]) ?> -

    - - $model, - 'attributes' => [ - 'id', - 'access_token', - 'prefix', - 'created', - 'updated', - ], - ]) ?> - -
    diff --git a/application/frontend/views/job-admin/_form.php b/application/frontend/views/job-admin/_form.php deleted file mode 100644 index 1f050c10..00000000 --- a/application/frontend/views/job-admin/_form.php +++ /dev/null @@ -1,41 +0,0 @@ - - -
    - - - - field($model, 'request_id')->textInput(['maxlength' => true]) ?> - - field($model, 'git_url')->textInput(['maxlength' => true]) ?> - - field($model, 'app_id')->textInput(['maxlength' => true]) ?> - - field($model, 'publisher_id')->textInput(['maxlength' => true]) ?> - - field($model, 'client_id')->textInput(['maxlength' => true]) ?> - - field($model, 'existing_version_code')->textInput(['maxlength' => true]) ?> - - field($model, 'jenkins_build_url')->textInput(['maxlength' => true]) ?> - - field($model, 'jenkins_publish_url')->textInput(['maxlength' => true]) ?> - - field($model, 'created')->textInput() ?> - - field($model, 'updated')->textInput() ?> - -
    - isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
    - - - -
    diff --git a/application/frontend/views/job-admin/create.php b/application/frontend/views/job-admin/create.php deleted file mode 100644 index fa1ab5c1..00000000 --- a/application/frontend/views/job-admin/create.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Create Job'; -$this->params['breadcrumbs'][] = ['label' => 'Jobs', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/job-admin/index.php b/application/frontend/views/job-admin/index.php deleted file mode 100644 index 11eff727..00000000 --- a/application/frontend/views/job-admin/index.php +++ /dev/null @@ -1,45 +0,0 @@ -title = 'Jobs'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - 'btn btn-success']) ?> -

    - - $dataProvider, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - - 'id', - 'request_id', - 'git_url:url', - 'app_id', - 'publisher_id', - [ - 'attribute' => 'client_id', - 'format' => 'html', - 'value' => function($data) { - return $data->client_id ? Html::a($data->client_id, ['client-admin/view', 'id' => $data->client_id]) : "(not set)"; - }, - ], - 'existing_version_code', - // 'created', - // 'updated', - - ['class' => 'yii\grid\ActionColumn'], - ], - ]); ?> - -
    diff --git a/application/frontend/views/job-admin/update.php b/application/frontend/views/job-admin/update.php deleted file mode 100644 index 94dc256f..00000000 --- a/application/frontend/views/job-admin/update.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Update Job: ' . ' ' . $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Jobs', 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; -$this->params['breadcrumbs'][] = 'Update'; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/job-admin/view.php b/application/frontend/views/job-admin/view.php deleted file mode 100644 index b7527fd0..00000000 --- a/application/frontend/views/job-admin/view.php +++ /dev/null @@ -1,57 +0,0 @@ -title = $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Jobs', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - $model->id], ['class' => 'btn btn-primary']) ?> - $model->id], [ - 'class' => 'btn btn-danger', - 'data' => [ - 'confirm' => 'Are you sure you want to delete this item?', - 'method' => 'post', - ], - ]) ?> -

    - - $model, - 'attributes' => [ - 'id', - 'request_id', - 'git_url:url', - 'app_id', - 'publisher_id', - [ - 'attribute'=>'client_id', - 'format'=>"html", - 'value' => $model->client_id ? Html::a($model->client_id, ['client-admin/view', 'id' => $model->client_id]) : "(not set)", - ], - 'existing_version_code', - [ - 'attribute'=>'jenkins_build_url', - 'format'=>"html", - 'value' => $model->jenkins_build_url ? Html::a($model->jenkins_build_url, $model->jenkins_build_url) : "(not set)", - ], - [ - 'attribute'=>'jenkins_publish_url', - 'format'=>"html", - 'value' => $model->jenkins_publish_url ? Html::a($model->jenkins_publish_url, $model->jenkins_publish_url) : "(not set)", - ], - 'created', - 'updated', - ], - ]) ?> - -
    diff --git a/application/frontend/views/layouts/main.php b/application/frontend/views/layouts/main.php deleted file mode 100644 index 6b12e20b..00000000 --- a/application/frontend/views/layouts/main.php +++ /dev/null @@ -1,77 +0,0 @@ - -beginPage() ?> - - - - - - - <?= Html::encode($this->title) ?> - head() ?> - - - beginBody() ?> -
    - 'SIL International', - 'brandUrl' => Yii::$app->homeUrl, - 'options' => [ - 'class' => 'navbar-inverse navbar-fixed-top', - ], - ]); - $menuItems = [ - ['label' => 'Home', 'url' => ['/site/index']], - ['label' => 'About', 'url' => ['/site/about']], - ['label' => 'Contact', 'url' => ['/site/contact']], - ]; - /* - if (Yii::$app->user->isGuest) { - $menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']]; - $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; - } else { - $menuItems[] = [ - 'label' => 'Logout (' . Yii::$app->user->identity->username . ')', - 'url' => ['/site/logout'], - 'linkOptions' => ['data-method' => 'post'] - ]; - }*/ - echo Nav::widget([ - 'options' => ['class' => 'navbar-nav navbar-right'], - 'items' => $menuItems, - ]); - NavBar::end(); - ?> - -
    - isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], - ]) ?> - - -
    -
    - -
    -
    -

    © SIL International

    -

    -
    -
    - - endBody() ?> - - -endPage() ?> diff --git a/application/frontend/views/operation-queue-admin/_form.php b/application/frontend/views/operation-queue-admin/_form.php deleted file mode 100644 index 6d0071f4..00000000 --- a/application/frontend/views/operation-queue-admin/_form.php +++ /dev/null @@ -1,41 +0,0 @@ - - -
    - - - - field($model, 'operation')->textInput(['maxlength' => true]) ?> - - field($model, 'operation_object_id')->textInput() ?> - - field($model, 'operation_parms')->textInput(['maxlength' => true]) ?> - - field($model, 'attempt_count')->textInput() ?> - - field($model, 'last_attempt')->textInput() ?> - - field($model, 'try_after')->textInput() ?> - - field($model, 'start_time')->textInput() ?> - - field($model, 'last_error')->textInput(['maxlength' => true]) ?> - - field($model, 'created')->textInput() ?> - - field($model, 'updated')->textInput() ?> - -
    - isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
    - - - -
    diff --git a/application/frontend/views/operation-queue-admin/create.php b/application/frontend/views/operation-queue-admin/create.php deleted file mode 100644 index 118b33ef..00000000 --- a/application/frontend/views/operation-queue-admin/create.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Create Operation Queue'; -$this->params['breadcrumbs'][] = ['label' => 'Operation Queues', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/operation-queue-admin/index.php b/application/frontend/views/operation-queue-admin/index.php deleted file mode 100644 index 460599eb..00000000 --- a/application/frontend/views/operation-queue-admin/index.php +++ /dev/null @@ -1,41 +0,0 @@ -title = 'Operation Queues'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - 'btn btn-success']) ?> -

    - - $dataProvider, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - - 'id', - 'operation', - 'operation_object_id', - 'operation_parms', - 'attempt_count', - // 'last_attempt', - // 'try_after', - // 'start_time', - // 'last_error', - // 'created', - // 'updated', - - ['class' => 'yii\grid\ActionColumn'], - ], - ]); ?> - -
    diff --git a/application/frontend/views/operation-queue-admin/update.php b/application/frontend/views/operation-queue-admin/update.php deleted file mode 100644 index 6445d50b..00000000 --- a/application/frontend/views/operation-queue-admin/update.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Update Operation Queue: ' . ' ' . $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Operation Queues', 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; -$this->params['breadcrumbs'][] = 'Update'; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/operation-queue-admin/view.php b/application/frontend/views/operation-queue-admin/view.php deleted file mode 100644 index 358e4d59..00000000 --- a/application/frontend/views/operation-queue-admin/view.php +++ /dev/null @@ -1,45 +0,0 @@ -title = $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Operation Queues', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - $model->id], ['class' => 'btn btn-primary']) ?> - $model->id], [ - 'class' => 'btn btn-danger', - 'data' => [ - 'confirm' => 'Are you sure you want to delete this item?', - 'method' => 'post', - ], - ]) ?> -

    - - $model, - 'attributes' => [ - 'id', - 'operation', - 'operation_object_id', - 'operation_parms', - 'attempt_count', - 'last_attempt', - 'try_after', - 'start_time', - 'last_error', - 'created', - 'updated', - ], - ]) ?> - -
    diff --git a/application/frontend/views/project-admin/_form.php b/application/frontend/views/project-admin/_form.php deleted file mode 100644 index 0fd831ae..00000000 --- a/application/frontend/views/project-admin/_form.php +++ /dev/null @@ -1,47 +0,0 @@ - - -
    - - - - field($model, 'status')->textInput(['maxlength' => true]) ?> - - field($model, 'result')->textInput(['maxlength' => true]) ?> - - field($model, 'error')->textInput(['maxlength' => true]) ?> - - field($model, 'url')->textInput(['maxlength' => true]) ?> - - field($model, 'user_id')->textInput(['maxlength' => true]) ?> - - field($model, 'group_id')->textInput(['maxlength' => true]) ?> - - field($model, 'app_id')->textInput(['maxlength' => true]) ?> - - field($model, 'client_id')->textInput(['maxlength' => true]) ?> - - field($model, 'project_name')->textInput(['maxlength' => true]) ?> - - field($model, 'language_code')->textInput(['maxlength' => true]) ?> - - field($model, 'publishing_key')->textInput(['maxlength' => true]) ?> - - isNewRecord ? "" : $form->field($model, 'created')->textInput() ?> - - isNewRecord ? "" : $form->field($model, 'updated')->textInput() ?> - -
    - isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
    - - - -
    diff --git a/application/frontend/views/project-admin/create.php b/application/frontend/views/project-admin/create.php deleted file mode 100644 index fef3ca15..00000000 --- a/application/frontend/views/project-admin/create.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Create Project'; -$this->params['breadcrumbs'][] = ['label' => 'Projects', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/project-admin/index.php b/application/frontend/views/project-admin/index.php deleted file mode 100644 index 223add47..00000000 --- a/application/frontend/views/project-admin/index.php +++ /dev/null @@ -1,41 +0,0 @@ -title = 'Projects'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - 'btn btn-success']) ?> -

    - - $dataProvider, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - - 'id', - 'status', - 'result', -// 'error', - 'url', - 'user_id', - 'group_id', - 'app_id', - 'project_name', -// 'language_code', -// 'publishing_key', - - ['class' => 'yii\grid\ActionColumn'], - ], - ]); ?> - -
    diff --git a/application/frontend/views/project-admin/update.php b/application/frontend/views/project-admin/update.php deleted file mode 100644 index 2e0d5a7b..00000000 --- a/application/frontend/views/project-admin/update.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Update Client: ' . ' ' . $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Projects', 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; -$this->params['breadcrumbs'][] = 'Update'; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/project-admin/view.php b/application/frontend/views/project-admin/view.php deleted file mode 100644 index 7e85b8d8..00000000 --- a/application/frontend/views/project-admin/view.php +++ /dev/null @@ -1,52 +0,0 @@ -title = $model->id; -$this->params['breadcrumbs'][] = ['label' => 'Projects', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - $model->id], ['class' => 'btn btn-primary']) ?> - $model->id], [ - 'class' => 'btn btn-danger', - 'data' => [ - 'confirm' => 'Are you sure you want to delete this item?', - 'method' => 'post', - ], - ]) ?> -

    - - $model, - 'attributes' => [ - 'id', - 'status', - 'result', - 'error', - 'url', - 'user_id', - 'group_id', - 'app_id', - 'project_name', - 'language_code', - [ - 'attribute'=>'client_id', - 'format'=>"html", - 'value' => $model->client_id ? Html::a($model->client_id, ['client-admin/view', 'id' => $model->client_id]) : "(not set)", - ], - 'publishing_key', - 'created', - 'updated', - ], - ]) ?> - -
    diff --git a/application/frontend/views/release-admin/_form.php b/application/frontend/views/release-admin/_form.php deleted file mode 100644 index 971dd080..00000000 --- a/application/frontend/views/release-admin/_form.php +++ /dev/null @@ -1,51 +0,0 @@ - - -
    - - - - field($model, 'build_id')->textInput() ?> - - field($model, 'status')->textInput(['maxlength' => true]) ?> - - field($model, 'created')->textInput() ?> - - field($model, 'updated')->textInput() ?> - - field($model, 'result')->textInput(['maxlength' => true]) ?> - - field($model, 'error')->textInput(['maxlength' => true]) ?> - - field($model, 'channel')->textInput(['maxlength' => true]) ?> - - field($model, 'title')->textInput(['maxlength' => true]) ?> - - field($model, 'defaultLanguage')->textInput(['maxlength' => true]) ?> - - field($model, 'build_guid')->textInput() ?> - - field($model, 'promote_from')->textInput(['maxlength' => true]) ?> - - field($model, 'targets')->textInput() ?> - - field($model, 'environment')->textInput() ?> - - field($model, 'artifact_url_base')->textInput(['maxlength' => true]) ?> - - field($model, 'artifact_files')->textInput(['maxlength' => true]) ?> - -
    - isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> -
    - - - -
    diff --git a/application/frontend/views/release-admin/create.php b/application/frontend/views/release-admin/create.php deleted file mode 100644 index b1a32eff..00000000 --- a/application/frontend/views/release-admin/create.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Create Release'; -$this->params['breadcrumbs'][] = ['label' => 'Releases', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/release-admin/index.php b/application/frontend/views/release-admin/index.php deleted file mode 100644 index d07700ff..00000000 --- a/application/frontend/views/release-admin/index.php +++ /dev/null @@ -1,65 +0,0 @@ -title = 'Releases'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - 'btn btn-success']) ?> -

    - - $dataProvider, - 'columns' => [ - ['class' => 'yii\grid\SerialColumn'], - - 'id', - [ - 'label' => 'Job ID', - 'attribute' => 'job_id', - 'format' => 'html', - 'value' => function($data) { - $jobId = $data->jobId(); - return Html::a("$jobId", ['job-admin/view', 'id' => $jobId]); - }, - ], - [ - 'attribute' => 'build_id', - 'format' => 'html', - 'value' => function($data) { - return Html::a("$data->build_id", ['build-admin/view', 'id' => $data->build_id]); - }, - ], - 'status', - 'result', - [ - 'attribute'=>'build_guid', - 'format'=>"html", - 'value' => function($data) { - return Html::a("$data->build_guid", $data->codebuild_url); - } - ], - // 'created', - // 'updated', - // 'result', - // 'error', - // 'channel', - // 'title', - // 'defaultLanguage', - // 'build_guid', - // 'promote_from', - - ['class' => 'yii\grid\ActionColumn'], - ], - ]); ?> - -
    diff --git a/application/frontend/views/release-admin/update.php b/application/frontend/views/release-admin/update.php deleted file mode 100644 index ccb0eed0..00000000 --- a/application/frontend/views/release-admin/update.php +++ /dev/null @@ -1,21 +0,0 @@ -title = 'Update Release: ' . ' ' . $model->title; -$this->params['breadcrumbs'][] = ['label' => 'Releases', 'url' => ['index']]; -$this->params['breadcrumbs'][] = ['label' => $model->title, 'url' => ['view', 'id' => $model->id]]; -$this->params['breadcrumbs'][] = 'Update'; -?> -
    - -

    title) ?>

    - - render('_form', [ - 'model' => $model, - ]) ?> - -
    diff --git a/application/frontend/views/release-admin/view.php b/application/frontend/views/release-admin/view.php deleted file mode 100644 index fc8c79c4..00000000 --- a/application/frontend/views/release-admin/view.php +++ /dev/null @@ -1,63 +0,0 @@ -title = $model->title; -$this->params['breadcrumbs'][] = ['label' => 'Releases', 'url' => ['index']]; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    - -

    - $model->id], ['class' => 'btn btn-primary']) ?> - $model->id], [ - 'class' => 'btn btn-danger', - 'data' => [ - 'confirm' => 'Are you sure you want to delete this item?', - 'method' => 'post', - ], - ]) ?> -

    - - $model, - 'attributes' => [ - 'id', - [ - 'attribute'=>'build_id', - 'format'=>"html", - 'value' => Html::a($model->build_id, ['build-admin/view', 'id' => $model->build_id]), - ], - 'status', - 'created', - 'updated', - 'result', - 'error:url', - [ - 'attribute' => 'artifacts', - 'format'=>'html', - 'value'=> Html::a("cloudWatch", $model->cloudWatch()) . ", " . - Html::a("consoleText", $model->consoleText()) . ", " . - Html::a("publishUrl", $model->publishUrl()) - ], - 'channel', - 'title', - 'defaultLanguage', - [ - 'attribute'=>'build_guid', - 'format'=>"html", - 'value' => Html::a($model->build_guid, $model->codebuild_url), - ], - 'promote_from', - 'targets', - 'environment' - ], - ]) ?> - -
    diff --git a/application/frontend/views/site/about.php b/application/frontend/views/site/about.php deleted file mode 100644 index 75bba8ed..00000000 --- a/application/frontend/views/site/about.php +++ /dev/null @@ -1,28 +0,0 @@ -title = 'App Publishing Service'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    - -

    title) ?>

    -

    Copyright 2011-2016 SIL International

    - - -

    Credits

    -

    Chris Hubbard (SIL International) : Dev Lead, Programming -

    -

    David Moore (SIL International) : Programming -

    -

    Rick MacLean (SIL International) : Dev Ops -

    - -
    diff --git a/application/frontend/views/site/contact.php b/application/frontend/views/site/contact.php deleted file mode 100644 index 396c051c..00000000 --- a/application/frontend/views/site/contact.php +++ /dev/null @@ -1,34 +0,0 @@ -title = 'Contact'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    -

    title) ?>

    - -

    - If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. -

    - -
    -
    - 'contact-form']); ?> - field($model, 'name') ?> - field($model, 'email') ?> - field($model, 'subject') ?> - field($model, 'body')->textArea(['rows' => 6]) ?> -
    - 'btn btn-primary', 'name' => 'contact-button']) ?> -
    - -
    -
    - -
    diff --git a/application/frontend/views/site/error.php b/application/frontend/views/site/error.php deleted file mode 100644 index b9812c48..00000000 --- a/application/frontend/views/site/error.php +++ /dev/null @@ -1,27 +0,0 @@ -title = $name; -?> -
    - -

    title) ?>

    - -
    - -
    - -

    - The above error occurred while the Web server was processing your request. -

    -

    - Please contact us if you think this is a server error. Thank you. -

    - -
    diff --git a/application/frontend/views/site/index.php b/application/frontend/views/site/index.php deleted file mode 100644 index ae0d853d..00000000 --- a/application/frontend/views/site/index.php +++ /dev/null @@ -1,73 +0,0 @@ -title = 'SIL App Builder Administration'; -?> -
    - -
    -

    App Publishing Service

    -

    Administration

    -
    - -
    - -
    -
    -

    Job

    - -

    View, edit, or remove entries from the job table. - Jobs point to the AWS S3 repository that contains the source for - the builds and publishes associated with this job. - Deleting job entries also deletes any associated - builds and releases associated with the job.

    - -

    Job Administration »

    -
    -
    -

    Build

    - -

    View, edit, or remove entries from the build table. Each entry in this table contains a url link - to the instance of the associated AWS Codebuild build attempt. Deleting the build also - deletes any releases associated with this build.

    - -

    Build Administration »

    -
    -
    -

    Release

    - -

    View, edit, or remove entries from the release table. Entries in this table relate to attempts to publish - builds in Google Play store or other customized locations. Each entry in this table contains a url link - to the instance of the associated AWS Codebuild publish attempt.

    - -

    Release Administration »

    -
    -
    -

    Client

    - -

    View, edit, or remove entries from the client table. Used if multiple Scriptoria sites are sending requests to - the build engine. Access tokens, which are used for the Authentication: Bearer fields of requests are entered - along with a prefix that is used in naming jobs associated with this client. - -

    Client Administration »

    -
    -
    -

    Operation Queue

    - -

    View, edit, or remove entries from the operation queue table. Entries in this table relate to internal operations - that are either queued to be performed, waiting to be retried, or have failed the maximum number of times and - are present for reporting purposes only. - -

    Operation Queue Administration »

    -
    -
    -

    Project

    - -

    View, edit, or remove entries from the project table. - Each entry contains a link to be used by Scripture App Builder to create or update a source repository in AWS S3. - -

    Project Administration »

    -
    -
    - -
    -
    diff --git a/application/frontend/views/site/login.php b/application/frontend/views/site/login.php deleted file mode 100644 index 647e04bd..00000000 --- a/application/frontend/views/site/login.php +++ /dev/null @@ -1,32 +0,0 @@ -title = 'Login'; -$this->params['breadcrumbs'][] = $this->title; -?> - diff --git a/application/frontend/views/site/requestPasswordResetToken.php b/application/frontend/views/site/requestPasswordResetToken.php deleted file mode 100644 index fa5055c6..00000000 --- a/application/frontend/views/site/requestPasswordResetToken.php +++ /dev/null @@ -1,27 +0,0 @@ -title = 'Request password reset'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    -

    title) ?>

    - -

    Please fill out your email. A link to reset password will be sent there.

    - -
    -
    - 'request-password-reset-form']); ?> - field($model, 'email') ?> -
    - 'btn btn-primary']) ?> -
    - -
    -
    -
    diff --git a/application/frontend/views/site/resetPassword.php b/application/frontend/views/site/resetPassword.php deleted file mode 100644 index 45461d03..00000000 --- a/application/frontend/views/site/resetPassword.php +++ /dev/null @@ -1,27 +0,0 @@ -title = 'Reset password'; -$this->params['breadcrumbs'][] = $this->title; -?> -
    -

    title) ?>

    - -

    Please choose your new password:

    - -
    -
    - 'reset-password-form']); ?> - field($model, 'password')->passwordInput() ?> -
    - 'btn btn-primary']) ?> -
    - -
    -
    -
    diff --git a/application/frontend/views/site/signup.php b/application/frontend/views/site/signup.php deleted file mode 100644 index cdf0143d..00000000 --- a/application/frontend/views/site/signup.php +++ /dev/null @@ -1,29 +0,0 @@ -title = 'Signup'; -$this->params['breadcrumbs'][] = $this->title; -?> - diff --git a/application/frontend/web/.gitignore b/application/frontend/web/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/application/frontend/web/.htaccess b/application/frontend/web/.htaccess deleted file mode 100644 index 6008ff56..00000000 --- a/application/frontend/web/.htaccess +++ /dev/null @@ -1,6 +0,0 @@ -RewriteEngine on -# If a directory or a file exists, use it directly -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# Otherwise forward it to index.php -RewriteRule . index.php \ No newline at end of file diff --git a/application/frontend/web/SILLogoBlue132x184.png b/application/frontend/web/SILLogoBlue132x184.png deleted file mode 100644 index 14895e5a38a5b862087ccb682911a9db2e107d10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12602 zcmcJ0Wl&pR^zFr=cq#5qf#U8IFAl{i?(XjHQi@aDq4=*zaZ;qXyM&;{r8x9|`)1zz z_GaFfmzgA!G zUg0Aucnd(ahyh=txyb9e0|55UzdKyvcNGlqO=1rjT@OuXD-Ul|H%q|V+ndeS$===E z)Wwp`+08olRD=Wor~n0N$&WsHCttmN-ps!YZgMy0ZphOXMNJvRyph61#Qg~$7E^h< zt#vWz5T}5sex_x9Op~I_m&jyvjD!v!hbfstVR!HjQQoxr>$_uu~A(bU_`vP+F6us~v9sBR+it5bNw(QmS)L!%`m(;?I!RL4Z7 zgc0Pl$6*cT5ub3p5*pC^lW@HxmpJ>`P=#gxzaMlL%#O-0M~r;e(KotWRMR~3F2&Om zxpo{d=2s%w3YSo|zP|KWcs|-qkicH=aZ36dh=@r77paBG)V2`PZ};4zmB>_CJ=8av zE}*A7AL(sI`of6BSy#Y+N;*q=n|K(WvSD=clHZ&s*HBLy7PziNA#SW6ewMrkdo0mu zJbF(oO`~YpzS@g(v26TOw<8jLDD~vJnI+c7UdsP)?b7(bt7xCwAjn9$NjW}LU-3%4 zo}e~JnY3Y#{T(VH3dIF?wCQrK>8BOsN*(m!HCHU>Cl7dh&);mL(MWgpufq0;^0UYi zApUZ+Q?u*Q zZk2Sk2mj|aXe@`578clO7R2O4yt7z!w#Z}GaHb&`S3`Qynj5|%@)w-PbR{k)=Mu_u z*C%1m@w_K{pVJ?-OK!ytnww|v3m%U~$mSB;T_qPY>m@?hT{Y={mV)XKf3SZsz5S`N zv!f$P*Ut}aZ8>@je_qGEd>}xjq3#_(!$Y|ikPz3ES$o^AA6 zM3N3l#r#kEcuLd*X}u%D#q$dkHpE~WWK-Z3Y`wZ{R{9h~K$^VU%3N20bvck!*4irk zEoKtSWPIZ4n;7NFEW-3G3XBpDrFUInqprm(pZ#YTudurQ>fGs(H{5#buY=mZVWl7cT=k`iug()t1*La}oFnB91Fmq~3mT*#U!dwc% z<0Fu(ay7O4VdV5E;zjJv#+j{}{zI(QMEcy;^Ex4Pk7+1^%9$fpHYz+}p!njsa3SvQ z>V4HR&ySGsbvw7CLt?4(mrduW@a&jT5=rT8>>i z0mOnFUJG!K-_q)^xT>yD-f=y;{dl|$&7Zit9C`bh#{YPrOF&&W@^g+XYg<+Qh`a3V zWk2>1x|-VjRGqz1RqfE@4>pNG_be{84mmE*6iRSXR27A*N;#g%ugw~Igu5U1-<;>Q z5Pi>Q8+->2WV(jjKBY^HAF(c}+<&nrpSeGI2ps?ltoKd-mbsT=duw-R5X+2(o|%BjbHL$rMxY5l>3zSUCK84<}h@K z0h7a5iLQUf8H@CV0e*Xt^0Sitib?Po>Fmp?GxyPrAwBs!TBRT2VYt2>T={AG z^~36Ck8G;F=$k(7R_rv9mvJbI&t+QR*Br9Xt{7(bSCROeS!8c!N5yISu#_t@?X3Ggw)92lNb+q-GhSqz z0g9rwln(Pz<3sDLRnOnS47|@$V`6nlJ{PB{Uq^{NFqd0{gx>m!xK14K4Vly`WTI|Y zRfTg5Pp=$8L>6w?-?9djp2f5z9i0*CwUmt=y&0*fq6dQgIHg zsXLZeKEEi_?*3lPt~bV+#|nSkI1==fZfMl;$wXtl=jME7ofZnS^D}OL&ir+;9IANo zVZTd!QZdJl+=6T)fAe0FUXo7avd`(uVm4dkwl&rf7c6-k>EgK7^xU2MIsDnD6R2s; zVV@Z1oSMX(w%TLKzkhJ~QA7PrUf1hWYYRR4(9OV($!3z(bnpBxzbEr`qqKWh*R52a zvjdfiK-#5`IAHSoSd||}S$s+(NDgkPpFKgAShPO>%aM8h1b)-#IYEylm9za`_gHnE$bZ zV+lS9(CQ;Hc^O+qr1nkLo}MH4SF3{G z9OBAa75&Lo2(@yrrM;p`zgg3X;^j}cHJekOK>wpm&L!(3+u2W2?-FrlI!Q9YlUx(- zXtnUX=sS)7s=_}tOFWrP4AHw8H?+Mx*oiye)*~r zJc>38PqA8$^5AO_499JPAi2g#gTsbZzJDJqw0w`R3|^7)xI4yNyQ$->TQ{m6>w`ao z_B*20IvJ62fq*g%H-ZK2`KWm$PR;%_s@KrWw3(8SJ)7C1hUu2YJ`IuEbzFr>$WoAK zgTR=_$UK4c4ExB*0K8DTm0V*7RjD7a?2G?7O?b~{^yqwIl2M)RB111uao1D+<@Y z!AlkC)d8Z@XiIq{@G2Lms-7YLg)jM!KYJycYGrqY#UCXkrp{s?H zAbDBjQLf9s?ibRSD~Kj>%j-{V=mEGxJXkX3eX9!!DhV>ztCE z+`$Q@ZQX(aNWmX;XKKg)m7`vnN(!5(<8)F(HPzTVu|xTE(|2Hyb-_#- zS^gy~hqyg~JEMrHME$2}$&52u3yFF@2q#uef3};=+Q0~9{-(K2ZAB3LhJcY;? z7$vs0Dk;qORee#BGZRYdavE{W5ImAfb48} zRnYT_m*CcJ$~RS0F&~wEyYA{*p|G)CJ#y)s^$Jwf1SJgrcx|hXr%H@Ek)``}st+Ij zp9O5WxyUe^A7VV<0%{s2j*;&RedV_=e0s*XWd5y0fqw4N@9v#sqa&no>kwa9x&A%E z20SCg%m_shl!E6^Ae$t!cLnPHh$?)1`THi~{&j!eqUq70nZv|LPf@2{JbTks1OH;n z^iRDBSYrDgO3fOX<@p!Xz`5-9f+RC7$&YhY^qXex^wO)Y!H?TKAZ&6n7v@; z(Iw}yNS69hWl@;Fb z7i900Gtta!RFkz8=U1+Up4@&%k*$2+(eNez(xa&Qv%XFI4HsUxmc~To@#tSz7%*ig zu#&e3wX+n}&e7`uQ!0j7%8%yedy%}qjmYaW^28wqB=ZNHzijcX=X)7xpV3pIZmuPy zaeojLKB*_&`keq?F%9;PL<*-vw8mzZ(-k9Dkimcm_0i0vaKH&mlX3kJLAvA z46R*=Tk0(JaW1;NDL!J!n6XqV?)#*;l}R@Kw`0k<)~+*A9p7YBz6S#NJIQ7_;y z@wRu?n4Cwbr$aqFVq9?8$0uwgfPRhp73J9|FDqRR<;tQ(72FecI*#497-KpsgrS?G zP%6sx1q2W@z`DLG{rMorD46WM7j1Ui+`N(yeL@9uSsa##K)P=c+B@2dDH0Xcf99X6 z_x!{LBLe5vo05M@1fpkXP{(uozZT25zvg_SfY9ZAKQ|OSK6vxbrVS5v5_gx20OMbh zI_(@IItyEeCN?R%Y?8tnJ|UPVI7*R>P0miKG}-8Sk04BWYui!oi|d3U)JxaM;`u$?Dq24Sy)>8+q{*LneI48y3iRp+E(wfKh=)?{V-AIu z$#y-J+ssPL7T+lLWZWpRl5Ap6Dvr4yx9dhJ?w0S%)rO912P;;strcf{#50P9hUPn- zDi?uqa;-xYp3E#(I!h39E3-#wR(Ych?Gi5&#VcQ74RNeN?~NjDe*yIZ;Rkzq=T3U? zGCt!Gi>4NZzRBC4lW(D;;hBm3RH~HQ?1H8iim&+RnKg<|wNk2ibQ}H0Q{T+YoW%{ol0jvotp2ayTjl*3^o!U029FVo zcpGdJs#I@wzmJ}lUkPO;9c|g}o&3gXEM9$o|9I&2A6gx6WonC-pJTOLVt1|L}e z-o#i0*t<6t4_;sNXNjF`9{pgknTfN4DevN!}0HtC3a1Vnh<;7#RnS%(! ziRT^6Fn0NxUv48#Z@{sc<9l;JcAy@Y0BRu%`Ik>>Lck}g7~IhO&qt!Gc#F{U^bZcA zj((av68nsIf>y{HlqBrLQH8=L6Wmam%)ZI4E6y4x#9VX`R-gl2?I&?eS7F&zmJ*BU zi5;FCSlt|?v?~Yc9{B$5qvw&*nKP2&y7Ty;qyqIQJ@I~FokDzY@TOS(KxpSH=>V_7 z@vA5#ACtfC-dj+#@0cfxM)`kFD|gB-uoF)FrHayo&dT#YFFKH zCj-49$3fa5PKg87s!D?h=8l9FRb4w;bCR|U6OhCxl~W8*vS@0eC=5DH^emVx98vv@ zGgg7#>};;mm;MzInl6VvuZ-r3gV4@^b)KFF_a6Zt?!TU*J}dV0rT#3r*l4}I4Qq8W zW2>A$xc{bk&z}+`uv$NfmC+@wIUc+7rfdN`=Sfoce?yE@uCrj9IqDn5|BMS=RRkr5 zlj=*?cuZS!6q1R&>MUui2mRSuM+oe-zc<#or_@+NlB0Sct?nlrgn;$2!LZX|3W7Zx z>HbUSWU7aFhY@!oguR6Cj+I+CQyJFVo8Ah!(X5H{NEZ_UhcstDXmuJ(5<|ftlH0S$~;7TM42eG30x!l@%4>OGWY z!Igc~*DCcyF#aDseorcG!DX}b}VO2LdaYYmSe+^i(R6T`?n_+-s5-;02!@{N3 zMzE%_0!tyi-k)CGh_0*VdH7nrvR^R;f^$}V9m4(AD3`Sqy9+By2WN@Vg9gq_8{1*$ z-roJ_Cm0E)iXcPc-21LxQ3fMz_6LnW2$pUW?6zV$VCr^8wps!x5@n2mK?k;}ABHU;c&Syw8LZSF+v)fP*C zW^a?^F<19@h_-{swg27Z;FM<)JDW871Oo%NK(DmxmwW9zMcO-q{<-b&3_>T*pm=Wm z3hO6t%Px{kmLs7#cuIn;P*aAEsy}v)HJ<{>Ey+$vmTR744Tuw|^!-SycnR<#zWzm9 zSXz5;UEy#3zr8u>Xv99wsAXCNPWP-s0c5WZl(g``f4!weo~pZtJPsBYmO*g^Ecd*M zygP+_HTC5j(IGZ}qY7!_h8c!TM6d<7+u!J8_q(ueBJO8HI~5qH?e$0J{^L~g)sLkt zhr5Rfgl8nNF}8!^0V&BWX3nH>_eI}Vb5~cE3~~N;T2f_Ghs{i7*LyL~6Mc1Jx+c5~fyExKeAUhOI-yc=cNChBZGYZNg`jJF% zfXx^l%7HcmIeS}_exMQv?i|&bgoA%>2mkZq^)hDV*7t^P_tKVnW53bnrK&H+s!QTp z$6({r2aw8q1V&s}r}AA~w;G=vVn@^ljV7>&W{1$9J3py?T#qTysaL2wI>6v( zjal4}GJ!4y5nW$0)Min}`4$CY*z>dep407g!VVHK3Tb@yGckBl8n`v;w5Kzgi{8m? z+15E8e>Wf@&L8kN_c7x7CQO@73a3u1=PO^^=kS2F2J3rPi&KH*v*b@d`JtNNn!xJj zPC9(PxVA#)DU)CUwwN;%B+fin4S!NTp6N|jI9 zQ8<2c@<7|Q;B8%0K`!l$xHlsRR?F2VOJ3iJ0{z=ecEfTTu5V14Y8?Al#S5g9M9oj zfTr<3Uq$>YQ?GMHJSY!Cz~(#=W$4dOh;hMXvix@aoAY%k*C44#bUoE&(nfkih-d&_1OrYzchR$&AThiq9AmUc zG~qdunS-&EFk_M=^g5c+1oX59auVZ&c*Rz7KPhyQ>F1y@pcaX{<yq&SsRh>N;FZs3 zg#TmzvOu!iUPc8k`t@SsNi9aY&GV0QlsxCZ#SOL5;)cC^Mcv$4wUMkHF7E!7(g8Dc~#Y59(ptB;6oI}^9$S@h=RRE21^0;T0)m@WW;7ZDn z7Z<-=@Z8tn<(HTd8;A~)(!;)ob=pJzzk{=k@+aDAH)pHcI@H~AEj39KcQWRSi&Ze4 zbx?eSMlnyT43O`s-BwJ-uuo=xW$Zy&&!#Bw$8j%%L@igsFJ@!mLGuMw7Lg>AwY=b~ zljhw~0l9g{`H&(5HU#txNqkj14Ow9t&33x|djjf?dR94`5YUr-@+ihh;N$?vDjszR zP4b}qo-9psuQ@AMGATbLy?D9fh&>HSzggtjPJGAc(pXDCIQl!~X-jv_frib?;O^)Q z_6733=;|TF{^UZ6&v|4iPiHWsC3;Yb(~$$qxC%0w>y~0*6c6?`cd+d2z0bdK_~JEf zllD-wDhO~|H=b@3GFHaseufUA#1BkW;&SZ$c_%ynwW{**1lnSR`rM_=PUv7H@x{FO zah0H14)HyB+JC0Mqk;jZfsWpagZ))#)n4@a8i44bQqdVFgn@p}@nd-Ka|F`iw*A?U zj~mq|v!9U@>vESeqAv=A(8Hq{ulb4jsszPa+eM{Iv-orui8k}noafh39ob^`IR0`+ z-H{?a2Kj1YS&$77s;W5Azam80Fstdz5j1ka1kH2Z6xABNz6{CXdhOr8@7^LjkI5E2 zA4*fd2)i(fsbx)bDGS~tM4CTRgERy-dR_uO<0oYXfBJ0(X9%=&b%?<&4q98N zF6c<{PxSDi;CtGEY6V?vBQstBApn=t5htl9fFE2xX;)7KP~cK6EnqV7FT@;@ zI`Jnr1>xP_`! zxwQH1Tn^mmK{F`yM;e*Te4=jf0oR=VS5uG0NG=>y5U0*P-TPk@ckd?iVsjPqP;tJVVb9?fYw7#mZ z)iFOgjklRYJ%Db9L0jJcDNwd6XS z7$&b<*bfo?KW~>(BP=DWG+xgxXmyYSP|{1P-DE zmK{VsqtUzB5nEeRa%Jb|hd%*L%+J%MrKNT8Dl`Knh5n0y@8L_yiv;HTwGj!OzC=|K!bj|?Nadl`t^0l z{M$qB&uN>P3A0(BKU*$EvT<^%myZpjI=u+Lwf%PcYfLG4cJr8|V|~g~Lw_$kK^~Xz zTNWI^oc>G7q{$rqZdpZ1vl{6ZLs}b^VM7+;+Jz86u#@>k6swX%Ej5$Flbqm@_`pyoN8F2HZY>JapHt z_||S<^0bKI<7(@1XZy~zO39>PXi&QJb)$&cHhU%ui+|-2Sr4KbQYxmUiNaJ<>$dru zV1UVltd*5b{{lPtL*LJN$s39gzjxv2Xk*52&e3|Y;3UkR zBpvA91&6>P(S(zt%Asz<6spH^iA@b1lpr~6l`$AJrT=xJ%Oyq;nevAq05N!XERX|L zj^jV)7k{HW`b=pUwnEJ}#glZK}`QQwDmY}iUVv~1&*X&lN#gsO^@ zyBd1aoI+(@7R#VeODwE;5kSYq<1L$znU0|$gg(#>*5c+x1Z=}HKh9N7Cx-+eU&bV> zKlNQ5TOHKAVK2yU2DIU_{Yru5+`Jg1)YMcNS667HG*LgUop}lVQ9*2+&?p`nKtNw+ z*;%eMo<~7hvb20DW@cvgA=Z>DgdD&jeYD{0MV5-FpVGaZzuZ-w1BlqGEm6KbD+#~% zw*MGL6d7`H#+vBIj4hE5bcTmbI?U-t zDS|@pFtOR~UlJHy$%yP=#k3xh`7!+x*JwaYf)&?$F;_F!fRA%CduO#{W7nB*`E-^1 ze8D%@vvJ`H)d?s>P?eAkMQzIfBGM9{uH7z-ri>bxE^jbHy2YhXy?Uez+2B4U7qQeQk?Wmwb#;v@#Cm&p*a{h9 zdWnhGenRhUHko6lk~T9d#^@a{^;|+YAW6YE|Hj6$fe4Vb9a6!NoxKZJQE50`yTWBH z+=-@k%BNeH+fm;Dsr^~Q$lS%u9Cr*AR%`%(oYP0d?bin1h=b!tS7s$*bapU&y{{ZT z>;;5;Naw`~`GNejre$mUOq_yV+`|Z#O1;vk&&+JAS>8*3D|eK5y{f^RC0eko*;!ad zBZ&q_h`u>w8><>`;s7*hyyKs;vk$SO2df}U1!!n!`hgv3#I*cn%+GL&BI1hEj)>6H zPn=|cv`H*D%xm$bVCn#+E$yGOEPPzV!tAfE9UMF$`~VQ;oVV+cIeiHEJ3;g?^zlnx`F9y^?e@Auf*D*;hQwFeqDs{iD{Gpe9zD;s}Jen3j5@%)UgEYZXJK zokaql0D_WLPvH}fckAU54YQ*E=O;MtkpYFO!&DPiR0%x1Mt(Ng3#rV#Umxfy%Gs&S z!uyEKm3Ab_P>Nr{vrPSJ^wpD=j-{_}Wo8-GS)db9!AR~9I5&ktp?@-zHc5y`(tph* zq#>3<^g22J|;6AH$Ph<~Kx%YP;}F?7;x4F(V!8M+7w~{zVsw z?3|I_6f~-(fLG*F7DW9a&lyUyGckDs%48gDA|i(Tzzz&HYmDCTyQzs=!&tv@8^K%7 z`VS274QbRIHwP4Kv#*Hd7H-_g|fBF{hXGj!-7V$KAEv`za_WOpNv2b0S~THmvQLvv6}iTat{K zg(S-+vDdNhX;s&T!p}~iv>mq{#GI6$hKEp{cTl^~5MlLguLah=4DLhXa=SeyJ^& z?;xp4e^b7MRbi()ug966jTXekO(2^+0jedzsF8AH+xhDd;*Ia}<}g5W>&My4Qy zt0P2=`=*258n5IrEwfb)H$Oj*l>nn45ah%Mw9Cr=++JV!s|>^&5HAHVxOXc44H-gz zc)blq4G104Xh~|Klx$<-aDA+OHwRt; zKCSHURf>A$w~sj)G05k-vuNh1mMYbv;1NJDh^JCQKpl_3fiRbsWe>2 z#Ms!Xg;stZGCUs+C`sWn9EP>InOSvnQ0>1NzbRO&bjt~O(ZE?lshDs0=fEIVVS4=cee5R)zlP!$u;H7}=ki>t5fw&D5 z;?g4d?W_B@&TFZVPkS+5fP{wKAT1;-*w6#wmC6y<8+|W%RM1VRW!~u{uY@LV=&5)2?^<32S3#~LZ zwAiTvfj?X2xmbl21J6R*w}S@)1?*o$$V_z7tH&6nlSp92H2CFZ%CxcB-KsPl>2L<0 zKYz|6S{k59m6KF&e^2M+YnZUz+SYd7Pp7pvj!|oEVPWwO7>*%Dm`eKcCBYSAM>6lm zYp*a!3HjvsE+*!QMFzj5*M*asTJi@7-T=J~8IgdU^vV>MR+ zz(^67oa93a@Q`650Ss;^88XseViF=b`~MXAL@$24%LA_0RaC4$EC)Oz=!1OP^N*!2 z+B75TuFoqSeTXEan!A+WHN=`QE##_8M8ujbVFL5JGIAn;PoiIJiJ=KRgRHJ@NmhB+yVAS_g!s^zt`pI-+lY^j81(g`Y4fs1J9<+{b+a?HRL)qX6b zND->+x^v-$H$W@xpyMfXk-4bzL&Sy#7YiP!Cy0ixRV#|AL<|8t3@KkKYiNID7>IgI zL`}J?G;U_@a5waY_U<7g$mhk{o8841Kp+k!uWbC(R`f&S(;|)Zmth8)= zr?(o8OfNls7ad3#z<__e?m+QEA2PoM!Durg0?|>+cSJ1)6<<(@jF3F_ZWBepTmd5a z4{K?bY>}BSZ*OnMe*JPYrAq<^h%N2*-pGDLXwU>D5FxN__9mGtsw)AKf1w_?I~f_x ze^&Tfo=j)u3DBvixaBQFMpiQ6AduG9&WM+_u#ZWl5u>3g@F9?o=&<4}EicEKQnf@# zcpsA`4A50^IWtVDN4{4_GYiG2bRdxW@GGYekRW5=l10(f)bz*I#Umm%6CcewrwZW* zBt{`>$lpn)1b<3qjiy=A1E@-36otoGSwx?X&9OB54i)v=XE8V|>J8gxJhiklumKcc z{$YQsh}~8q!3yUvh_NfWncqCe{mjDB_f@9=Zebco?uu1oufF&`WXObV4aWpaWM^=q zOJf07W*9{eox-5O@rvfJ$Pg2b1jiy>0^)fzs?vVeIXqAcUPEhdN9YTpp9Gzw5a8qQ zKZ+DJlog{0)ZJnthc`0z&c-XnLxIIsg+c?^8z|}LSuR6~w#1khKpv1X#@y$C>rH+z z{)3iupugSWS->cAqkFX=xSE}zk#0*YOnk@1pM89lVWw98EgR8s8q3ABP;=rijDt2ot-QPkzAz(3JO#Yf5YDlgU7uNK4FbqQ#DwJc*)LaL5d%_c ze=*?_+sdeXm+{sj!Q?=}+>wKo6bNN~$)TpJ;VO~Aq9;=iGW6IXSifB+N>$g@u=Ars zSS#EuHs8i@zIP0%o14!>4flV1JY4nXL8F1+QK{oL9~j_g*5qb;rSzRaAw3&GC8(^s z2+p#`F}Kwq`(x>aGuqmY;-VfTI#tdZs7Xx zmA=LB~T-g)>jeWA6$fwA+b{@@=-+Y{IS_MHr9(f<}TvP(Vg`#CLTnkn;}!>M1* z5jmq^2jAMxHr1pP8?>pZsZyneeASTteCqU#z)Qy6@USLu?&S9nofLNLF6C*Kc<2lk zvj@(2Y`i)vlE=E}HZKa=TIbzCMU!xjk-mOTvi6SHA^w*CS$`yYTr^u596$!Te+#UJyKC2|O$TNR;J^^pSJqNcYJCqfe=uVA=kb-Sd5en@ELeRj^h zP>cn{1v!M4*6`jqD?(9aJWEkJI6gksaBkigF61u)3O776U(huw_&*0gK}JQo?!8Ia F{{RU`S|b1e diff --git a/application/frontend/web/assets/.gitignore b/application/frontend/web/assets/.gitignore deleted file mode 100644 index d6b7ef32..00000000 --- a/application/frontend/web/assets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/application/frontend/web/css/site.css b/application/frontend/web/css/site.css deleted file mode 100644 index 698be709..00000000 --- a/application/frontend/web/css/site.css +++ /dev/null @@ -1,91 +0,0 @@ -html, -body { - height: 100%; -} - -.wrap { - min-height: 100%; - height: auto; - margin: 0 auto -60px; - padding: 0 0 60px; -} - -.wrap > .container { - padding: 70px 15px 20px; -} - -.footer { - height: 60px; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - padding-top: 20px; -} - -.jumbotron { - text-align: center; - background-color: transparent; -} - -.jumbotron .btn { - font-size: 21px; - padding: 14px 24px; -} - -.not-set { - color: #c55; - font-style: italic; -} - -/* add sorting icons to gridview sort links */ -a.asc:after, a.desc:after { - position: relative; - top: 1px; - display: inline-block; - font-family: 'Glyphicons Halflings'; - font-style: normal; - font-weight: normal; - line-height: 1; - padding-left: 5px; -} - -a.asc:after { - content: /*"\e113"*/ "\e151"; -} - -a.desc:after { - content: /*"\e114"*/ "\e152"; -} - -.sort-numerical a.asc:after { - content: "\e153"; -} - -.sort-numerical a.desc:after { - content: "\e154"; -} - -.sort-ordinal a.asc:after { - content: "\e155"; -} - -.sort-ordinal a.desc:after { - content: "\e156"; -} - -.grid-view th { - white-space: nowrap; -} - -.hint-block { - display: block; - margin-top: 5px; - color: #999; -} - -.error-summary { - color: #a94442; - background: #fdf7f7; - border-left: 3px solid #eed3d7; - padding: 10px 20px; - margin: 0 0 15px 0; -} diff --git a/application/frontend/web/favicon.ico b/application/frontend/web/favicon.ico deleted file mode 100644 index 580ed732e86556ec57f3f3395a210246d679c076..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|4HXaJKC0wf0lAEr2iX{M9K3=BR0 y!E90pK{x=K$Oz&POT#sS8N$ZKhC)h8ip0_|-T#43{vnSYgXBQCu@O54$pHYIza?e> diff --git a/application/frontend/web/index-test.php b/application/frontend/web/index-test.php deleted file mode 100644 index 66a9520c..00000000 --- a/application/frontend/web/index-test.php +++ /dev/null @@ -1,18 +0,0 @@ -run(); diff --git a/application/frontend/web/index.php b/application/frontend/web/index.php deleted file mode 100644 index cb917597..00000000 --- a/application/frontend/web/index.php +++ /dev/null @@ -1,16 +0,0 @@ -run(); diff --git a/application/frontend/web/robots.txt b/application/frontend/web/robots.txt deleted file mode 100644 index 6f27bb66..00000000 --- a/application/frontend/web/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: \ No newline at end of file diff --git a/application/frontend/widgets/Alert.php b/application/frontend/widgets/Alert.php deleted file mode 100644 index 5b204889..00000000 --- a/application/frontend/widgets/Alert.php +++ /dev/null @@ -1,79 +0,0 @@ -getSession()->setFlash('error', 'This is the message'); - * \Yii::$app->getSession()->setFlash('success', 'This is the message'); - * \Yii::$app->getSession()->setFlash('info', 'This is the message'); - * ``` - * - * Multiple messages could be set as follows: - * - * ```php - * \Yii::$app->getSession()->setFlash('error', ['Error 1', 'Error 2']); - * ``` - * - * @author Kartik Visweswaran - * @author Alexander Makarov - */ -class Alert extends \yii\bootstrap\Widget -{ - /** - * @var array the alert types configuration for the flash messages. - * This array is setup as $key => $value, where: - * - $key is the name of the session flash variable - * - $value is the bootstrap alert type (i.e. danger, success, info, warning) - */ - public $alertTypes = [ - 'error' => 'alert-danger', - 'danger' => 'alert-danger', - 'success' => 'alert-success', - 'info' => 'alert-info', - 'warning' => 'alert-warning' - ]; - - /** - * @var array the options for rendering the close button tag. - */ - public $closeButton = []; - - public function init() - { - parent::init(); - - $session = \Yii::$app->getSession(); - $flashes = $session->getAllFlashes(); - $appendCss = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; - - foreach ($flashes as $type => $data) { - if (isset($this->alertTypes[$type])) { - $data = (array) $data; - foreach ($data as $i => $message) { - /* initialize css class for each alert box */ - $this->options['class'] = $this->alertTypes[$type] . $appendCss; - - /* assign unique id to each alert box */ - $this->options['id'] = $this->getId() . '-' . $type . '-' . $i; - - echo \yii\bootstrap\Alert::widget([ - 'body' => $message, - 'closeButton' => $this->closeButton, - 'options' => $this->options, - ]); - } - - $session->removeFlash($type); - } - } - } -} diff --git a/application/init b/application/init deleted file mode 100755 index e6278dba..00000000 --- a/application/init +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env php - - * - * @link http://www.yiiframework.com/ - * @copyright Copyright (c) 2008 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -if (!extension_loaded('mcrypt')) { - die('The mcrypt PHP extension is required by Yii2.'); -} - -$params = getParams(); -$root = str_replace('\\', '/', __DIR__); -$envs = require("$root/environments/index.php"); -$envNames = array_keys($envs); - -echo "Yii Application Initialization Tool v1.0\n\n"; - -$envName = null; -if (empty($params['env']) || $params['env'] === '1') { - echo "Which environment do you want the application to be initialized in?\n\n"; - foreach ($envNames as $i => $name) { - echo " [$i] $name\n"; - } - echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; - $answer = trim(fgets(STDIN)); - - if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { - echo "\n Quit initialization.\n"; - exit(0); - } - - if (isset($envNames[$answer])) { - $envName = $envNames[$answer]; - } -} else { - $envName = $params['env']; -} - -if (!in_array($envName, $envNames)) { - $envsList = implode(', ', $envNames); - echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; - exit(2); -} - -$env = $envs[$envName]; - -if (empty($params['env'])) { - echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; - $answer = trim(fgets(STDIN)); - if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit initialization.\n"; - exit(0); - } -} - -echo "\n Start initialization ...\n\n"; -$files = getFileList("$root/environments/{$env['path']}"); -$all = false; -foreach ($files as $file) { - if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all, $params)) { - break; - } -} - -$callbacks = ['setCookieValidationKey', 'setWritable', 'setExecutable']; -foreach ($callbacks as $callback) { - if (!empty($env[$callback])) { - $callback($root, $env[$callback]); - } -} - -echo "\n ... initialization completed.\n\n"; - -function getFileList($root, $basePath = '') -{ - $files = []; - $handle = opendir($root); - while (($path = readdir($handle)) !== false) { - if ($path === '.svn' || $path === '.' || $path === '..') { - continue; - } - $fullPath = "$root/$path"; - $relativePath = $basePath === '' ? $path : "$basePath/$path"; - if (is_dir($fullPath)) { - $files = array_merge($files, getFileList($fullPath, $relativePath)); - } else { - $files[] = $relativePath; - } - } - closedir($handle); - return $files; -} - -function copyFile($root, $source, $target, &$all, $params) -{ - if (!is_file($root . '/' . $source)) { - echo " skip $target ($source not exist)\n"; - return true; - } - if (is_file($root . '/' . $target)) { - if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { - echo " unchanged $target\n"; - return true; - } - if ($all) { - echo " overwrite $target\n"; - } else { - echo " exist $target\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - - - $answer = !empty($params['overwrite']) ? $params['overwrite'] : trim(fgets(STDIN)); - if (!strncasecmp($answer, 'q', 1)) { - return false; - } else { - if (!strncasecmp($answer, 'y', 1)) { - echo " overwrite $target\n"; - } else { - if (!strncasecmp($answer, 'a', 1)) { - echo " overwrite $target\n"; - $all = true; - } else { - echo " skip $target\n"; - return true; - } - } - } - } - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; - } - echo " generate $target\n"; - @mkdir(dirname($root . '/' . $target), 0777, true); - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; -} - -function getParams() -{ - $rawParams = []; - if (isset($_SERVER['argv'])) { - $rawParams = $_SERVER['argv']; - array_shift($rawParams); - } - - $params = []; - foreach ($rawParams as $param) { - if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { - $name = $matches[1]; - $params[$name] = isset($matches[3]) ? $matches[3] : true; - } else { - $params[] = $param; - } - } - return $params; -} - -function setWritable($root, $paths) -{ - foreach ($paths as $writable) { - echo " chmod 0777 $writable\n"; - @chmod("$root/$writable", 0777); - } -} - -function setExecutable($root, $paths) -{ - foreach ($paths as $executable) { - echo " chmod 0755 $executable\n"; - @chmod("$root/$executable", 0755); - } -} - -function setCookieValidationKey($root, $paths) -{ - foreach ($paths as $file) { - echo " generate cookie validation key in $file\n"; - $file = $root . '/' . $file; - $length = 32; - $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); - $key = strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.'); - $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($file)); - file_put_contents($file, $content); - } -} - -function createSymlink($links) -{ - foreach ($links as $link => $target) { - echo " symlink $target as $link\n"; - if (!is_link($link)) { - symlink($target, $link); - } - } -} diff --git a/application/init.bat b/application/init.bat deleted file mode 100644 index e50c242c..00000000 --- a/application/init.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line init script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright (c) 2008 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%init" %* - -@endlocal diff --git a/application/quickstart.php b/application/quickstart.php deleted file mode 100644 index 99cd9590..00000000 --- a/application/quickstart.php +++ /dev/null @@ -1,96 +0,0 @@ -setApplicationName(APPLICATION_NAME); - $client->setScopes(SCOPES); - echo "Client Path " . CLIENT_SECRET_PATH . PHP_EOL; - $client->setAuthConfig(CLIENT_SECRET_PATH); - $client->setAccessType('offline'); - - // Load previously authorized credentials from a file. - $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH); - echo "Credentials Path: " . $credentialsPath . PHP_EOL; - if (file_exists($credentialsPath)) { - $accessToken = json_decode(file_get_contents($credentialsPath), true); - echo "Access token: " . $accessToken . PHP_EOL; - print_r($accessToken); - } else { - // Request authorization from the user. - $authUrl = $client->createAuthUrl(); - printf("Open the following link in your browser:\n%s\n", $authUrl); - print 'Enter verification code: '; - $authCode = trim(fgets(STDIN)); - - // Exchange authorization code for an access token. - $accessToken = $client->fetchAccessTokenWithAuthCode($authCode); - - // Store the credentials to disk. - if(!file_exists(dirname($credentialsPath))) { - mkdir(dirname($credentialsPath), 0700, true); - } - file_put_contents($credentialsPath, json_encode($accessToken)); - printf("Credentials saved to %s\n", $credentialsPath); - } - $client->setAccessToken($accessToken); - - // Refresh the token if it's expired. - if ($client->isAccessTokenExpired()) { - echo "Token expired".PHP_EOL; - $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken()); - file_put_contents($credentialsPath, json_encode($client->getAccessToken())); - } - return $client; -} - -/** - * Expands the home directory alias '~' to the full path. - * @param string $path the path to expand. - * @return string the expanded path. - */ -function expandHomeDirectory($path) { - $homeDirectory = getenv('HOME'); - if (empty($homeDirectory)) { - $homeDirectory = getenv("HOMEDRIVE") . getenv("HOMEPATH"); - } - return str_replace('~', realpath($homeDirectory), $path); -} - -// Get the API client and construct the service object. -$client = getClient(); -$service = new Google_Service_Drive($client); - -// Print the names and IDs for up to 10 files. -$optParams = array( - 'pageSize' => 10, - 'fields' => "nextPageToken, files(id, name)" -); -$results = $service->files->listFiles($optParams); - -if (count($results->getFiles()) == 0) { - print "No files found.\n"; -} else { - print "Files:\n"; - foreach ($results->getFiles() as $file) { - printf("%s (%s)\n", $file->getName(), $file->getId()); - } -} \ No newline at end of file diff --git a/application/rebuildbasemodels.sh b/application/rebuildbasemodels.sh deleted file mode 100755 index af7592c6..00000000 --- a/application/rebuildbasemodels.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -TABLES=(job build release email_queue operation_queue client project) -SUFFIX="Base" - -declare -A models -models["job"]="JobBase" -models["build"]="BuildBase" -models["release"]="ReleaseBase" -models["email_queue"]="EmailQueueBase" -models["operation_queue"]="OperationQueueBase" -models["client"]="ClientBase" -models["project"]="ProjectBase" - -for i in "${!models[@]}"; do - CMD="./yii gii/model --tableName=$i --modelClass=${models[$i]} --enableI18N=1 --overwrite=1 --interactive=0 --ns=\common\models" - echo $CMD - $CMD -done diff --git a/application/requirements.php b/application/requirements.php deleted file mode 100644 index fd84f479..00000000 --- a/application/requirements.php +++ /dev/null @@ -1,132 +0,0 @@ -Error'; - echo '

    The path to yii framework seems to be incorrect.

    '; - echo '

    You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . '.

    '; - echo '

    Please refer to the README on how to install Yii.

    '; -} - -require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); -$requirementsChecker = new YiiRequirementChecker(); - -$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; -$gdOK = $imagickOK = false; - -if (extension_loaded('imagick')) { - $imagick = new Imagick(); - $imagickFormats = $imagick->queryFormats('PNG'); - if (in_array('PNG', $imagickFormats)) { - $imagickOK = true; - } else { - $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; - } -} - -if (extension_loaded('gd')) { - $gdInfo = gd_info(); - if (!empty($gdInfo['FreeType Support'])) { - $gdOK = true; - } else { - $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; - } -} - -/** - * Adjust requirements according to your application specifics. - */ -$requirements = array( - // Database : - array( - 'name' => 'PDO extension', - 'mandatory' => true, - 'condition' => extension_loaded('pdo'), - 'by' => 'All DB-related classes', - ), - array( - 'name' => 'PDO SQLite extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_sqlite'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for SQLite database.', - ), - array( - 'name' => 'PDO MySQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_mysql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for MySQL database.', - ), - array( - 'name' => 'PDO PostgreSQL extension', - 'mandatory' => false, - 'condition' => extension_loaded('pdo_pgsql'), - 'by' => 'All DB-related classes', - 'memo' => 'Required for PostgreSQL database.', - ), - // Cache : - array( - 'name' => 'Memcache extension', - 'mandatory' => false, - 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), - 'by' => 'MemCache', - 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '' - ), - array( - 'name' => 'APC extension', - 'mandatory' => false, - 'condition' => extension_loaded('apc'), - 'by' => 'ApcCache', - ), - // CAPTCHA: - array( - 'name' => 'GD PHP extension with FreeType support', - 'mandatory' => false, - 'condition' => $gdOK, - 'by' => 'Captcha', - 'memo' => $gdMemo, - ), - array( - 'name' => 'ImageMagick PHP extension with PNG support', - 'mandatory' => false, - 'condition' => $imagickOK, - 'by' => 'Captcha', - 'memo' => $imagickMemo, - ), - // PHP ini : - 'phpExposePhp' => array( - 'name' => 'Expose PHP', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), - 'by' => 'Security reasons', - 'memo' => '"expose_php" should be disabled at php.ini', - ), - 'phpAllowUrlInclude' => array( - 'name' => 'PHP allow url include', - 'mandatory' => false, - 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), - 'by' => 'Security reasons', - 'memo' => '"allow_url_include" should be disabled at php.ini', - ), - 'phpSmtp' => array( - 'name' => 'PHP mail SMTP', - 'mandatory' => false, - 'condition' => strlen(ini_get('SMTP')) > 0, - 'by' => 'Email sending', - 'memo' => 'PHP mail SMTP server required', - ), -); -$requirementsChecker->checkYii()->check($requirements)->render(); diff --git a/application/run-cron.sh b/application/run-cron.sh deleted file mode 100755 index c8055418..00000000 --- a/application/run-cron.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -if [[ "x" == "x$LOGENTRIES_KEY" ]]; then - echo "Missing LOGENTRIES_KEY environment variable"; -else - # Set logentries key based on environment variable - sed -i /etc/rsyslog.conf -e "s/LOGENTRIESKEY/${LOGENTRIES_KEY}/" - # Start syslog - rsyslogd -fi - -# fix folder permissions -chown -R www-data:www-data \ - /data/console/runtime/ \ - /data/frontend/assets/ \ - /data/frontend/runtime/ \ - /data/frontend/web/assets/ - -# make sure rsyslog can read logentries cert -chmod a+r /opt/ssl/logentries.all.crt - -# Dump env to a file -touch /etc/cron.d/appbuilder -env | while read line ; do - if [[ "${line: -1}" != "=" ]] - then - echo "$line" >> /etc/cron.d/appbuilder - fi -done - -/data/yii cron/aws-startup 2>&1 >> /var/log/aws-startup.log - -# Add env vars to doorman-cron to make available to scripts -cat /etc/cron.d/appbuilder-cron >> /etc/cron.d/appbuilder - -# Remove original cron file without env vars -rm -f /etc/cron.d/appbuilder-cron - -# Start cron daemon -cron -f diff --git a/application/run-tests.sh b/application/run-tests.sh deleted file mode 100755 index d97d11c1..00000000 --- a/application/run-tests.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -# Run shell tests -scversion="stable" # or "v0.4.7", or "latest" -curl -s -L --output - "https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJv -cp "shellcheck-${scversion}/shellcheck" /usr/bin/ -shellcheck --version -shellcheck /data/console/views/cron/scripts/upload/default/*.sh || exit 1 - -# Run database migrations -whenavail db 3306 100 /data/yii migrate --interactive=0 -whenavail db 3306 100 /data/yii migrate --interactive=0 --migrationPath=console/migrations-test - -# Run codeception tests -cd /data -composer install --prefer-dist --no-interaction -./vendor/bin/codecept run unit diff --git a/application/run.sh b/application/run.sh deleted file mode 100755 index fced6fa9..00000000 --- a/application/run.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -if [[ "x" == "x$LOGENTRIES_KEY" ]]; then - echo "Missing LOGENTRIES_KEY environment variable"; -else - # Set logentries key based on environment variable - sed -i /etc/rsyslog.conf -e "s/LOGENTRIESKEY/${LOGENTRIES_KEY}/" - # Start syslog - rsyslogd -fi - -# fix folder permissions -chown -R www-data:www-data \ - /data/vendor/ \ - /data/console/runtime/ \ - /data/frontend/assets/ \ - /data/frontend/runtime/ \ - /data/frontend/web/assets/ - - - -# Run database migrations -/data/yii migrate --interactive=0 -/data/yii migrate --interactive=0 --migrationPath=console/migrations-local - -# Run apache in foreground -apache2ctl -D FOREGROUND diff --git a/application/tests/README.md b/application/tests/README.md deleted file mode 100644 index ad7f0167..00000000 --- a/application/tests/README.md +++ /dev/null @@ -1,58 +0,0 @@ -This directory contains various tests for the advanced applications. - -Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/). - -After creating and setting up the advanced application, follow these steps to prepare for the tests: - -1. Install Codeception if it's not yet installed: - - ``` - composer global require "codeception/codeception=2.0.*" "codeception/specify=*" "codeception/verify=*" - ``` - - If you've never used Composer for global packages run `composer global status`. It should output: - - ``` - Changed current directory to - ``` - - Then add `/vendor/bin` to you `PATH` environment variable. Now you're able to use `codecept` from command - line globally. - -2. Install faker extension by running the following from template root directory where `composer.json` is: - - ``` - composer require --dev yiisoft/yii2-faker:* - ``` - -3. Create `yii2_advanced_tests` database then update it by applying migrations: - - ``` - codeception/bin/yii migrate - ``` - -4. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in - webserver. In the root directory where `common`, `frontend` etc. are execute the following: - - ``` - php -S localhost:8080 - ``` - -5. Now you can run the tests with the following commands, assuming you are in the `tests/codeception` directory: - - ``` - # frontend tests - cd frontend - codecept build - codecept run - - # backend tests - - cd backend - codecept build - codecept run - - # etc. - ``` - - If you already have run `codecept build` for each application, you can skip that step and run all tests by a single `codecept run`. diff --git a/application/tests/_bootstrap.php b/application/tests/_bootstrap.php deleted file mode 100644 index fce31fbf..00000000 --- a/application/tests/_bootstrap.php +++ /dev/null @@ -1,17 +0,0 @@ -getScenario()->runStep(new \Codeception\Step\Action('setHeader', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Authenticates user for HTTP_AUTH - * - * @param $username - * @param $password - * @see \Codeception\Module\PhpBrowser::amHttpAuthenticated() - */ - public function amHttpAuthenticated($username, $password) { - return $this->getScenario()->runStep(new \Codeception\Step\Condition('amHttpAuthenticated', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Open web page at the given absolute URL and sets its hostname as the base host. - * - * ``` php - * amOnUrl('http://codeception.com'); - * $I->amOnPage('/quickstart'); // moves to http://codeception.com/quickstart - * ?> - * ``` - * @see \Codeception\Module\PhpBrowser::amOnUrl() - */ - public function amOnUrl($url) { - return $this->getScenario()->runStep(new \Codeception\Step\Condition('amOnUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Changes the subdomain for the 'url' configuration parameter. - * Does not open a page; use `amOnPage` for that. - * - * ``` php - * amOnSubdomain('user'); - * $I->amOnPage('/'); - * // moves to http://user.mysite.com/ - * ?> - * ``` - * - * @param $subdomain - * - * @return mixed - * @see \Codeception\Module\PhpBrowser::amOnSubdomain() - */ - public function amOnSubdomain($subdomain) { - return $this->getScenario()->runStep(new \Codeception\Step\Condition('amOnSubdomain', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Low-level API method. - * If Codeception commands are not enough, use [Guzzle HTTP Client](http://guzzlephp.org/) methods directly - * - * Example: - * - * ``` php - * executeInGuzzle(function (\GuzzleHttp\Client $client) { - * $client->get('/get', ['query' => ['foo' => 'bar']]); - * }); - * ?> - * ``` - * - * It is not recommended to use this command on a regular basis. - * If Codeception lacks important Guzzle Client methods, implement them and submit patches. - * - * @param callable $function - * @see \Codeception\Module\PhpBrowser::executeInGuzzle() - */ - public function executeInGuzzle($function) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('executeInGuzzle', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Sets the HTTP header to the passed value - which is used on - * subsequent HTTP requests through PhpBrowser. - * - * Example: - * ```php - * haveHttpHeader('X-Requested-With', 'Codeception'); - * $I->amOnPage('test-headers.php'); - * ?> - * ``` - * - * To use special chars in Header Key use HTML Character Entities: - * Example: - * Header with underscore - 'Client_Id' - * should be represented as - 'Client_Id' or 'Client_Id' - * - * ```php - * haveHttpHeader('Client_Id', 'Codeception'); - * ?> - * ``` - * - * @param string $name the name of the request header - * @param string $value the value to set it to for subsequent - * requests - * @see \Codeception\Lib\InnerBrowser::haveHttpHeader() - */ - public function haveHttpHeader($name, $value) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('haveHttpHeader', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Deletes the header with the passed name. Subsequent requests - * will not have the deleted header in its request. - * - * Example: - * ```php - * haveHttpHeader('X-Requested-With', 'Codeception'); - * $I->amOnPage('test-headers.php'); - * // ... - * $I->deleteHeader('X-Requested-With'); - * $I->amOnPage('some-other-page.php'); - * ?> - * ``` - * - * @param string $name the name of the header to delete. - * @see \Codeception\Lib\InnerBrowser::deleteHeader() - */ - public function deleteHeader($name) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('deleteHeader', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Opens the page for the given relative URI. - * - * ``` php - * amOnPage('/'); - * // opens /register page - * $I->amOnPage('/register'); - * ``` - * - * @param string $page - * @see \Codeception\Lib\InnerBrowser::amOnPage() - */ - public function amOnPage($page) { - return $this->getScenario()->runStep(new \Codeception\Step\Condition('amOnPage', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Perform a click on a link or a button, given by a locator. - * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string. - * For buttons, the "value" attribute, "name" attribute, and inner text are searched. - * For links, the link text is searched. - * For images, the "alt" attribute and inner text of any parent links are searched. - * - * The second parameter is a context (CSS or XPath locator) to narrow the search. - * - * Note that if the locator matches a button of type `submit`, the form will be submitted. - * - * ``` php - * click('Logout'); - * // button of form - * $I->click('Submit'); - * // CSS button - * $I->click('#form input[type=submit]'); - * // XPath - * $I->click('//form/*[@type="submit"]'); - * // link in context - * $I->click('Logout', '#nav'); - * // using strict locator - * $I->click(['link' => 'Login']); - * ?> - * ``` - * - * @param $link - * @param $context - * @see \Codeception\Lib\InnerBrowser::click() - */ - public function click($link, $context = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('click', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string (case insensitive). - * - * You can specify a specific HTML element (via CSS or XPath) as the second - * parameter to only search within that element. - * - * ``` php - * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page - * $I->see('Sign Up', '//body/h1'); // with XPath - * $I->see('Sign Up', ['css' => 'body h1']); // with strict CSS locator - * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->see('strong')` will return true for strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will *not* be true for strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. - * - * @param string $text - * @param array|string $selector optional - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::see() - */ - public function canSee($text, $selector = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('see', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string (case insensitive). - * - * You can specify a specific HTML element (via CSS or XPath) as the second - * parameter to only search within that element. - * - * ``` php - * see('Logout'); // I can suppose user is logged in - * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page - * $I->see('Sign Up', '//body/h1'); // with XPath - * $I->see('Sign Up', ['css' => 'body h1']); // with strict CSS locator - * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->see('strong')` will return true for strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will *not* be true for strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. - * - * @param string $text - * @param array|string $selector optional - * @see \Codeception\Lib\InnerBrowser::see() - */ - public function see($text, $selector = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('see', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page doesn't contain the text specified (case insensitive). - * Give a locator as the second parameter to match a specific region. - * - * ```php - * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath - * $I->dontSee('Sign Up', ['css' => 'body h1']); // with strict CSS locator - * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->dontSee('strong')` will fail on strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will ignore strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. - * - * @param string $text - * @param array|string $selector optional - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSee() - */ - public function cantSee($text, $selector = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSee', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page doesn't contain the text specified (case insensitive). - * Give a locator as the second parameter to match a specific region. - * - * ```php - * dontSee('Login'); // I can suppose user is already logged in - * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page - * $I->dontSee('Sign Up','//body/h1'); // with XPath - * $I->dontSee('Sign Up', ['css' => 'body h1']); // with strict CSS locator - * ``` - * - * Note that the search is done after stripping all HTML tags from the body, - * so `$I->dontSee('strong')` will fail on strings like: - * - * - `

    I am Stronger than thou

    ` - * - `` - * - * But will ignore strings like: - * - * - `Home` - * - `
    Home` - * - `` - * - * For checking the raw source code, use `seeInSource()`. - * - * @param string $text - * @param array|string $selector optional - * @see \Codeception\Lib\InnerBrowser::dontSee() - */ - public function dontSee($text, $selector = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSee', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ``` php - * seeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInSource() - */ - public function canSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInSource', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ``` php - * seeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * @see \Codeception\Lib\InnerBrowser::seeInSource() - */ - public function seeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInSource', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ```php - * dontSeeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInSource() - */ - public function cantSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInSource', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current page contains the given string in its - * raw source code. - * - * ```php - * dontSeeInSource('

    Green eggs & ham

    '); - * ``` - * - * @param $raw - * @see \Codeception\Lib\InnerBrowser::dontSeeInSource() - */ - public function dontSeeInSource($raw) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInSource', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there's a link with the specified text. - * Give a full URL as the second parameter to match links with that exact URL. - * - * ``` php - * seeLink('Logout'); // matches Logout - * $I->seeLink('Logout','/logout'); // matches Logout - * ?> - * ``` - * - * @param string $text - * @param string $url optional - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeLink() - */ - public function canSeeLink($text, $url = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeLink', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there's a link with the specified text. - * Give a full URL as the second parameter to match links with that exact URL. - * - * ``` php - * seeLink('Logout'); // matches Logout - * $I->seeLink('Logout','/logout'); // matches Logout - * ?> - * ``` - * - * @param string $text - * @param string $url optional - * @see \Codeception\Lib\InnerBrowser::seeLink() - */ - public function seeLink($text, $url = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeLink', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page doesn't contain a link with the given string. - * If the second parameter is given, only links with a matching "href" attribute will be checked. - * - * ``` php - * dontSeeLink('Logout'); // I suppose user is not logged in - * $I->dontSeeLink('Checkout now', '/store/cart.php'); - * ?> - * ``` - * - * @param string $text - * @param string $url optional - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeLink() - */ - public function cantSeeLink($text, $url = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeLink', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page doesn't contain a link with the given string. - * If the second parameter is given, only links with a matching "href" attribute will be checked. - * - * ``` php - * dontSeeLink('Logout'); // I suppose user is not logged in - * $I->dontSeeLink('Checkout now', '/store/cart.php'); - * ?> - * ``` - * - * @param string $text - * @param string $url optional - * @see \Codeception\Lib\InnerBrowser::dontSeeLink() - */ - public function dontSeeLink($text, $url = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeLink', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current URI contains the given string. - * - * ``` php - * seeInCurrentUrl('home'); - * // to match: /users/1 - * $I->seeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param string $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInCurrentUrl() - */ - public function canSeeInCurrentUrl($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInCurrentUrl', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current URI contains the given string. - * - * ``` php - * seeInCurrentUrl('home'); - * // to match: /users/1 - * $I->seeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param string $uri - * @see \Codeception\Lib\InnerBrowser::seeInCurrentUrl() - */ - public function seeInCurrentUrl($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInCurrentUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URI doesn't contain the given string. - * - * ``` php - * dontSeeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param string $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInCurrentUrl() - */ - public function cantSeeInCurrentUrl($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInCurrentUrl', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URI doesn't contain the given string. - * - * ``` php - * dontSeeInCurrentUrl('/users/'); - * ?> - * ``` - * - * @param string $uri - * @see \Codeception\Lib\InnerBrowser::dontSeeInCurrentUrl() - */ - public function dontSeeInCurrentUrl($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInCurrentUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL is equal to the given string. - * Unlike `seeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * seeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param string $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlEquals() - */ - public function canSeeCurrentUrlEquals($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlEquals', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL is equal to the given string. - * Unlike `seeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * seeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param string $uri - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlEquals() - */ - public function seeCurrentUrlEquals($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCurrentUrlEquals', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL doesn't equal the given string. - * Unlike `dontSeeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * dontSeeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param string $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlEquals() - */ - public function cantSeeCurrentUrlEquals($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlEquals', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL doesn't equal the given string. - * Unlike `dontSeeInCurrentUrl`, this only matches the full URL. - * - * ``` php - * dontSeeCurrentUrlEquals('/'); - * ?> - * ``` - * - * @param string $uri - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlEquals() - */ - public function dontSeeCurrentUrlEquals($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlEquals', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL matches the given regular expression. - * - * ``` php - * seeCurrentUrlMatches('~^/users/(\d+)~'); - * ?> - * ``` - * - * @param string $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlMatches() - */ - public function canSeeCurrentUrlMatches($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlMatches', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the current URL matches the given regular expression. - * - * ``` php - * seeCurrentUrlMatches('~^/users/(\d+)~'); - * ?> - * ``` - * - * @param string $uri - * @see \Codeception\Lib\InnerBrowser::seeCurrentUrlMatches() - */ - public function seeCurrentUrlMatches($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCurrentUrlMatches', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current url doesn't match the given regular expression. - * - * ``` php - * dontSeeCurrentUrlMatches('~^/users/(\d+)~'); - * ?> - * ``` - * - * @param string $uri - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlMatches() - */ - public function cantSeeCurrentUrlMatches($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlMatches', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that current url doesn't match the given regular expression. - * - * ``` php - * dontSeeCurrentUrlMatches('~^/users/(\d+)~'); - * ?> - * ``` - * - * @param string $uri - * @see \Codeception\Lib\InnerBrowser::dontSeeCurrentUrlMatches() - */ - public function dontSeeCurrentUrlMatches($uri) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeCurrentUrlMatches', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Executes the given regular expression against the current URI and returns the first capturing group. - * If no parameters are provided, the full URI is returned. - * - * ``` php - * grabFromCurrentUrl('~^/user/(\d+)/~'); - * $uri = $I->grabFromCurrentUrl(); - * ?> - * ``` - * - * @param string $uri optional - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabFromCurrentUrl() - */ - public function grabFromCurrentUrl($uri = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('grabFromCurrentUrl', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the specified checkbox is checked. - * - * ``` php - * seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. - * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); - * ?> - * ``` - * - * @param $checkbox - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCheckboxIsChecked() - */ - public function canSeeCheckboxIsChecked($checkbox) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCheckboxIsChecked', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the specified checkbox is checked. - * - * ``` php - * seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. - * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); - * ?> - * ``` - * - * @param $checkbox - * @see \Codeception\Lib\InnerBrowser::seeCheckboxIsChecked() - */ - public function seeCheckboxIsChecked($checkbox) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCheckboxIsChecked', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Check that the specified checkbox is unchecked. - * - * ``` php - * dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. - * ?> - * ``` - * - * @param $checkbox - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCheckboxIsChecked() - */ - public function cantSeeCheckboxIsChecked($checkbox) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCheckboxIsChecked', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Check that the specified checkbox is unchecked. - * - * ``` php - * dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms - * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. - * ?> - * ``` - * - * @param $checkbox - * @see \Codeception\Lib\InnerBrowser::dontSeeCheckboxIsChecked() - */ - public function dontSeeCheckboxIsChecked($checkbox) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeCheckboxIsChecked', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given input field or textarea *equals* (i.e. not just contains) the given value. - * Fields are matched by label text, the "name" attribute, CSS, or XPath. - * - * ``` php - * seeInField('Body','Type your comment here'); - * $I->seeInField('form textarea[name=body]','Type your comment here'); - * $I->seeInField('form input[type=hidden]','hidden_value'); - * $I->seeInField('#searchform input','Search'); - * $I->seeInField('//form/*[@name=search]','Search'); - * $I->seeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInField() - */ - public function canSeeInField($field, $value) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInField', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given input field or textarea *equals* (i.e. not just contains) the given value. - * Fields are matched by label text, the "name" attribute, CSS, or XPath. - * - * ``` php - * seeInField('Body','Type your comment here'); - * $I->seeInField('form textarea[name=body]','Type your comment here'); - * $I->seeInField('form input[type=hidden]','hidden_value'); - * $I->seeInField('#searchform input','Search'); - * $I->seeInField('//form/*[@name=search]','Search'); - * $I->seeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * @see \Codeception\Lib\InnerBrowser::seeInField() - */ - public function seeInField($field, $value) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInField', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that an input field or textarea doesn't contain the given value. - * For fuzzy locators, the field is matched by label text, CSS and XPath. - * - * ``` php - * dontSeeInField('Body','Type your comment here'); - * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); - * $I->dontSeeInField('form input[type=hidden]','hidden_value'); - * $I->dontSeeInField('#searchform input','Search'); - * $I->dontSeeInField('//form/*[@name=search]','Search'); - * $I->dontSeeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInField() - */ - public function cantSeeInField($field, $value) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInField', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that an input field or textarea doesn't contain the given value. - * For fuzzy locators, the field is matched by label text, CSS and XPath. - * - * ``` php - * dontSeeInField('Body','Type your comment here'); - * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); - * $I->dontSeeInField('form input[type=hidden]','hidden_value'); - * $I->dontSeeInField('#searchform input','Search'); - * $I->dontSeeInField('//form/*[@name=search]','Search'); - * $I->dontSeeInField(['name' => 'search'], 'Search'); - * ?> - * ``` - * - * @param $field - * @param $value - * @see \Codeception\Lib\InnerBrowser::dontSeeInField() - */ - public function dontSeeInField($field, $value) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInField', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if the array of form parameters (name => value) are set on the form matched with the - * passed selector. - * - * ``` php - * seeInFormFields('form[name=myform]', [ - * 'input1' => 'value', - * 'input2' => 'other value', - * ]); - * ?> - * ``` - * - * For multi-select elements, or to check values of multiple elements with the same name, an - * array may be passed: - * - * ``` php - * seeInFormFields('.form-class', [ - * 'multiselect' => [ - * 'value1', - * 'value2', - * ], - * 'checkbox[]' => [ - * 'a checked value', - * 'another checked value', - * ], - * ]); - * ?> - * ``` - * - * Additionally, checkbox values can be checked with a boolean. - * - * ``` php - * seeInFormFields('#form-id', [ - * 'checkbox1' => true, // passes if checked - * 'checkbox2' => false, // passes if unchecked - * ]); - * ?> - * ``` - * - * Pair this with submitForm for quick testing magic. - * - * ``` php - * 'value', - * 'field2' => 'another value', - * 'checkbox1' => true, - * // ... - * ]; - * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); - * // $I->amOnPage('/path/to/form-page') may be needed - * $I->seeInFormFields('//form[@id=my-form]', $form); - * ?> - * ``` - * - * @param $formSelector - * @param $params - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInFormFields() - */ - public function canSeeInFormFields($formSelector, $params) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInFormFields', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if the array of form parameters (name => value) are set on the form matched with the - * passed selector. - * - * ``` php - * seeInFormFields('form[name=myform]', [ - * 'input1' => 'value', - * 'input2' => 'other value', - * ]); - * ?> - * ``` - * - * For multi-select elements, or to check values of multiple elements with the same name, an - * array may be passed: - * - * ``` php - * seeInFormFields('.form-class', [ - * 'multiselect' => [ - * 'value1', - * 'value2', - * ], - * 'checkbox[]' => [ - * 'a checked value', - * 'another checked value', - * ], - * ]); - * ?> - * ``` - * - * Additionally, checkbox values can be checked with a boolean. - * - * ``` php - * seeInFormFields('#form-id', [ - * 'checkbox1' => true, // passes if checked - * 'checkbox2' => false, // passes if unchecked - * ]); - * ?> - * ``` - * - * Pair this with submitForm for quick testing magic. - * - * ``` php - * 'value', - * 'field2' => 'another value', - * 'checkbox1' => true, - * // ... - * ]; - * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); - * // $I->amOnPage('/path/to/form-page') may be needed - * $I->seeInFormFields('//form[@id=my-form]', $form); - * ?> - * ``` - * - * @param $formSelector - * @param $params - * @see \Codeception\Lib\InnerBrowser::seeInFormFields() - */ - public function seeInFormFields($formSelector, $params) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInFormFields', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if the array of form parameters (name => value) are not set on the form matched with - * the passed selector. - * - * ``` php - * dontSeeInFormFields('form[name=myform]', [ - * 'input1' => 'non-existent value', - * 'input2' => 'other non-existent value', - * ]); - * ?> - * ``` - * - * To check that an element hasn't been assigned any one of many values, an array can be passed - * as the value: - * - * ``` php - * dontSeeInFormFields('.form-class', [ - * 'fieldName' => [ - * 'This value shouldn\'t be set', - * 'And this value shouldn\'t be set', - * ], - * ]); - * ?> - * ``` - * - * Additionally, checkbox values can be checked with a boolean. - * - * ``` php - * dontSeeInFormFields('#form-id', [ - * 'checkbox1' => true, // fails if checked - * 'checkbox2' => false, // fails if unchecked - * ]); - * ?> - * ``` - * - * @param $formSelector - * @param $params - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInFormFields() - */ - public function cantSeeInFormFields($formSelector, $params) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInFormFields', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks if the array of form parameters (name => value) are not set on the form matched with - * the passed selector. - * - * ``` php - * dontSeeInFormFields('form[name=myform]', [ - * 'input1' => 'non-existent value', - * 'input2' => 'other non-existent value', - * ]); - * ?> - * ``` - * - * To check that an element hasn't been assigned any one of many values, an array can be passed - * as the value: - * - * ``` php - * dontSeeInFormFields('.form-class', [ - * 'fieldName' => [ - * 'This value shouldn\'t be set', - * 'And this value shouldn\'t be set', - * ], - * ]); - * ?> - * ``` - * - * Additionally, checkbox values can be checked with a boolean. - * - * ``` php - * dontSeeInFormFields('#form-id', [ - * 'checkbox1' => true, // fails if checked - * 'checkbox2' => false, // fails if unchecked - * ]); - * ?> - * ``` - * - * @param $formSelector - * @param $params - * @see \Codeception\Lib\InnerBrowser::dontSeeInFormFields() - */ - public function dontSeeInFormFields($formSelector, $params) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInFormFields', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Submits the given form on the page, with the given form - * values. Pass the form field's values as an array in the second - * parameter. - * - * Although this function can be used as a short-hand version of - * `fillField()`, `selectOption()`, `click()` etc. it has some important - * differences: - * - * * Only field *names* may be used, not CSS/XPath selectors nor field labels - * * If a field is sent to this function that does *not* exist on the page, - * it will silently be added to the HTTP request. This is helpful for testing - * some types of forms, but be aware that you will *not* get an exception - * like you would if you called `fillField()` or `selectOption()` with - * a missing field. - * - * Fields that are not provided will be filled by their values from the page, - * or from any previous calls to `fillField()`, `selectOption()` etc. - * You don't need to click the 'Submit' button afterwards. - * This command itself triggers the request to form's action. - * - * You can optionally specify which button's value to include - * in the request with the last parameter (as an alternative to - * explicitly setting its value in the second parameter), as - * button values are not otherwise included in the request. - * - * Examples: - * - * ``` php - * submitForm('#login', [ - * 'login' => 'davert', - * 'password' => '123456' - * ]); - * // or - * $I->submitForm('#login', [ - * 'login' => 'davert', - * 'password' => '123456' - * ], 'submitButtonName'); - * - * ``` - * - * For example, given this sample "Sign Up" form: - * - * ``` html - * - * Login: - *
    - * Password: - *
    - * Do you agree to our terms? - *
    - * Select pricing plan: - * - * - * - * ``` - * - * You could write the following to submit it: - * - * ``` php - * submitForm( - * '#userForm', - * [ - * 'user' => [ - * 'login' => 'Davert', - * 'password' => '123456', - * 'agree' => true - * ] - * ], - * 'submitButton' - * ); - * ``` - * Note that "2" will be the submitted value for the "plan" field, as it is - * the selected option. - * - * You can also emulate a JavaScript submission by not specifying any - * buttons in the third parameter to submitForm. - * - * ```php - * submitForm( - * '#userForm', - * [ - * 'user' => [ - * 'login' => 'Davert', - * 'password' => '123456', - * 'agree' => true - * ] - * ] - * ); - * ``` - * - * This function works well when paired with `seeInFormFields()` - * for quickly testing CRUD interfaces and form validation logic. - * - * ``` php - * 'value', - * 'field2' => 'another value', - * 'checkbox1' => true, - * // ... - * ]; - * $I->submitForm('#my-form', $form, 'submitButton'); - * // $I->amOnPage('/path/to/form-page') may be needed - * $I->seeInFormFields('#my-form', $form); - * ``` - * - * Parameter values can be set to arrays for multiple input fields - * of the same name, or multi-select combo boxes. For checkboxes, - * you can use either the string value or boolean `true`/`false` which will - * be replaced by the checkbox's value in the DOM. - * - * ``` php - * submitForm('#my-form', [ - * 'field1' => 'value', - * 'checkbox' => [ - * 'value of first checkbox', - * 'value of second checkbox', - * ], - * 'otherCheckboxes' => [ - * true, - * false, - * false - * ], - * 'multiselect' => [ - * 'first option value', - * 'second option value' - * ] - * ]); - * ``` - * - * Mixing string and boolean values for a checkbox's value is not supported - * and may produce unexpected results. - * - * Field names ending in `[]` must be passed without the trailing square - * bracket characters, and must contain an array for its value. This allows - * submitting multiple values with the same name, consider: - * - * ```php - * submitForm('#my-form', [ - * 'field[]' => 'value', - * 'field[]' => 'another value', // 'field[]' is already a defined key - * ]); - * ``` - * - * The solution is to pass an array value: - * - * ```php - * submitForm('#my-form', [ - * 'field' => [ - * 'value', - * 'another value', - * ] - * ]); - * ``` - * - * @param $selector - * @param $params - * @param $button - * @see \Codeception\Lib\InnerBrowser::submitForm() - */ - public function submitForm($selector, $params, $button = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('submitForm', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Fills a text field or textarea with the given string. - * - * ``` php - * fillField("//input[@type='text']", "Hello World!"); - * $I->fillField(['name' => 'email'], 'jon@mail.com'); - * ?> - * ``` - * - * @param $field - * @param $value - * @see \Codeception\Lib\InnerBrowser::fillField() - */ - public function fillField($field, $value) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('fillField', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Selects an option in a select tag or in radio button group. - * - * ``` php - * selectOption('form select[name=account]', 'Premium'); - * $I->selectOption('form input[name=payment]', 'Monthly'); - * $I->selectOption('//form/select[@name=account]', 'Monthly'); - * ?> - * ``` - * - * Provide an array for the second argument to select multiple options: - * - * ``` php - * selectOption('Which OS do you use?', array('Windows','Linux')); - * ?> - * ``` - * - * Or provide an associative array for the second argument to specifically define which selection method should be used: - * - * ``` php - * selectOption('Which OS do you use?', array('text' => 'Windows')); // Only search by text 'Windows' - * $I->selectOption('Which OS do you use?', array('value' => 'windows')); // Only search by value 'windows' - * ?> - * ``` - * - * @param $select - * @param $option - * @see \Codeception\Lib\InnerBrowser::selectOption() - */ - public function selectOption($select, $option) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('selectOption', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Ticks a checkbox. For radio buttons, use the `selectOption` method instead. - * - * ``` php - * checkOption('#agree'); - * ?> - * ``` - * - * @param $option - * @see \Codeception\Lib\InnerBrowser::checkOption() - */ - public function checkOption($option) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('checkOption', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Unticks a checkbox. - * - * ``` php - * uncheckOption('#notify'); - * ?> - * ``` - * - * @param $option - * @see \Codeception\Lib\InnerBrowser::uncheckOption() - */ - public function uncheckOption($option) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('uncheckOption', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Attaches a file relative to the Codeception `_data` directory to the given file upload field. - * - * ``` php - * attachFile('input[@type="file"]', 'prices.xls'); - * ?> - * ``` - * - * @param $field - * @param $filename - * @see \Codeception\Lib\InnerBrowser::attachFile() - */ - public function attachFile($field, $filename) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('attachFile', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * If your page triggers an ajax request, you can perform it manually. - * This action sends a GET ajax request with specified params. - * - * See ->sendAjaxPostRequest for examples. - * - * @param $uri - * @param $params - * @see \Codeception\Lib\InnerBrowser::sendAjaxGetRequest() - */ - public function sendAjaxGetRequest($uri, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('sendAjaxGetRequest', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * If your page triggers an ajax request, you can perform it manually. - * This action sends a POST ajax request with specified params. - * Additional params can be passed as array. - * - * Example: - * - * Imagine that by clicking checkbox you trigger ajax request which updates user settings. - * We emulate that click by running this ajax request manually. - * - * ``` php - * sendAjaxPostRequest('/updateSettings', array('notifications' => true)); // POST - * $I->sendAjaxGetRequest('/updateSettings', array('notifications' => true)); // GET - * - * ``` - * - * @param $uri - * @param $params - * @see \Codeception\Lib\InnerBrowser::sendAjaxPostRequest() - */ - public function sendAjaxPostRequest($uri, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('sendAjaxPostRequest', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * If your page triggers an ajax request, you can perform it manually. - * This action sends an ajax request with specified method and params. - * - * Example: - * - * You need to perform an ajax request specifying the HTTP method. - * - * ``` php - * sendAjaxRequest('PUT', '/posts/7', array('title' => 'new title')); - * - * ``` - * - * @param $method - * @param $uri - * @param $params - * @see \Codeception\Lib\InnerBrowser::sendAjaxRequest() - */ - public function sendAjaxRequest($method, $uri, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('sendAjaxRequest', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Finds and returns the text contents of the given element. - * If a fuzzy locator is used, the element is found using CSS, XPath, - * and by matching the full page source by regular expression. - * - * ``` php - * grabTextFrom('h1'); - * $heading = $I->grabTextFrom('descendant-or-self::h1'); - * $value = $I->grabTextFrom('~ - * ``` - * - * @param $cssOrXPathOrRegex - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabTextFrom() - */ - public function grabTextFrom($cssOrXPathOrRegex) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('grabTextFrom', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Grabs the value of the given attribute value from the given element. - * Fails if element is not found. - * - * ``` php - * grabAttributeFrom('#tooltip', 'title'); - * ?> - * ``` - * - * - * @param $cssOrXpath - * @param $attribute - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabAttributeFrom() - */ - public function grabAttributeFrom($cssOrXpath, $attribute) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('grabAttributeFrom', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Grabs either the text content, or attribute values, of nodes - * matched by $cssOrXpath and returns them as an array. - * - * ```html - * First - * Second - * Third - * ``` - * - * ```php - * grabMultiple('a'); - * - * // would return ['#first', '#second', '#third'] - * $aLinks = $I->grabMultiple('a', 'href'); - * ?> - * ``` - * - * @param $cssOrXpath - * @param $attribute - * @return string[] - * @see \Codeception\Lib\InnerBrowser::grabMultiple() - */ - public function grabMultiple($cssOrXpath, $attribute = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('grabMultiple', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * @param $field - * - * @return array|mixed|null|string - * @see \Codeception\Lib\InnerBrowser::grabValueFrom() - */ - public function grabValueFrom($field) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('grabValueFrom', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Sets a cookie with the given name and value. - * You can set additional cookie params like `domain`, `path`, `expires`, `secure` in array passed as last argument. - * - * ``` php - * setCookie('PHPSESSID', 'el4ukv0kqbvoirg7nkp4dncpk3'); - * ?> - * ``` - * - * @param $name - * @param $val - * @param array $params - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::setCookie() - */ - public function setCookie($name, $val, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('setCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Grabs a cookie value. - * You can set additional cookie params like `domain`, `path` in array passed as last argument. - * - * @param $cookie - * - * @param array $params - * @return mixed - * @see \Codeception\Lib\InnerBrowser::grabCookie() - */ - public function grabCookie($cookie, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('grabCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Grabs current page source code. - * - * @throws ModuleException if no page was opened. - * - * @return string Current page source code. - * @see \Codeception\Lib\InnerBrowser::grabPageSource() - */ - public function grabPageSource() { - return $this->getScenario()->runStep(new \Codeception\Step\Action('grabPageSource', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that a cookie with the given name is set. - * You can set additional cookie params like `domain`, `path` as array passed in last argument. - * - * ``` php - * seeCookie('PHPSESSID'); - * ?> - * ``` - * - * @param $cookie - * @param array $params - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeCookie() - */ - public function canSeeCookie($cookie, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCookie', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that a cookie with the given name is set. - * You can set additional cookie params like `domain`, `path` as array passed in last argument. - * - * ``` php - * seeCookie('PHPSESSID'); - * ?> - * ``` - * - * @param $cookie - * @param array $params - * @return mixed - * @see \Codeception\Lib\InnerBrowser::seeCookie() - */ - public function seeCookie($cookie, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there isn't a cookie with the given name. - * You can set additional cookie params like `domain`, `path` as array passed in last argument. - * - * @param $cookie - * - * @param array $params - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeCookie() - */ - public function cantSeeCookie($cookie, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCookie', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there isn't a cookie with the given name. - * You can set additional cookie params like `domain`, `path` as array passed in last argument. - * - * @param $cookie - * - * @param array $params - * @return mixed - * @see \Codeception\Lib\InnerBrowser::dontSeeCookie() - */ - public function dontSeeCookie($cookie, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Unsets cookie with the given name. - * You can set additional cookie params like `domain`, `path` in array passed as last argument. - * - * @param $cookie - * - * @param array $params - * @return mixed - * @see \Codeception\Lib\InnerBrowser::resetCookie() - */ - public function resetCookie($name, $params = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Action('resetCookie', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element exists on the page and is visible. - * You can also specify expected attributes of this element. - * - * ``` php - * seeElement('.error'); - * $I->seeElement('//form/input[1]'); - * $I->seeElement('input', ['name' => 'login']); - * $I->seeElement('input', ['value' => '123456']); - * - * // strict locator in first arg, attributes in second - * $I->seeElement(['css' => 'form input'], ['name' => 'login']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * @return - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeElement() - */ - public function canSeeElement($selector, $attributes = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeElement', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element exists on the page and is visible. - * You can also specify expected attributes of this element. - * - * ``` php - * seeElement('.error'); - * $I->seeElement('//form/input[1]'); - * $I->seeElement('input', ['name' => 'login']); - * $I->seeElement('input', ['value' => '123456']); - * - * // strict locator in first arg, attributes in second - * $I->seeElement(['css' => 'form input'], ['name' => 'login']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * @return - * @see \Codeception\Lib\InnerBrowser::seeElement() - */ - public function seeElement($selector, $attributes = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeElement', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element is invisible or not present on the page. - * You can also specify expected attributes of this element. - * - * ``` php - * dontSeeElement('.error'); - * $I->dontSeeElement('//form/input[1]'); - * $I->dontSeeElement('input', ['name' => 'login']); - * $I->dontSeeElement('input', ['value' => '123456']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeElement() - */ - public function cantSeeElement($selector, $attributes = null) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeElement', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given element is invisible or not present on the page. - * You can also specify expected attributes of this element. - * - * ``` php - * dontSeeElement('.error'); - * $I->dontSeeElement('//form/input[1]'); - * $I->dontSeeElement('input', ['name' => 'login']); - * $I->dontSeeElement('input', ['value' => '123456']); - * ?> - * ``` - * - * @param $selector - * @param array $attributes - * @see \Codeception\Lib\InnerBrowser::dontSeeElement() - */ - public function dontSeeElement($selector, $attributes = null) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeElement', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there are a certain number of elements matched by the given locator on the page. - * - * ``` php - * seeNumberOfElements('tr', 10); - * $I->seeNumberOfElements('tr', [0,10]); // between 0 and 10 elements - * ?> - * ``` - * @param $selector - * @param mixed $expected int or int[] - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeNumberOfElements() - */ - public function canSeeNumberOfElements($selector, $expected) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberOfElements', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that there are a certain number of elements matched by the given locator on the page. - * - * ``` php - * seeNumberOfElements('tr', 10); - * $I->seeNumberOfElements('tr', [0,10]); // between 0 and 10 elements - * ?> - * ``` - * @param $selector - * @param mixed $expected int or int[] - * @see \Codeception\Lib\InnerBrowser::seeNumberOfElements() - */ - public function seeNumberOfElements($selector, $expected) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumberOfElements', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is selected. - * - * ``` php - * seeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeOptionIsSelected() - */ - public function canSeeOptionIsSelected($selector, $optionText) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeOptionIsSelected', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is selected. - * - * ``` php - * seeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::seeOptionIsSelected() - */ - public function seeOptionIsSelected($selector, $optionText) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeOptionIsSelected', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is not selected. - * - * ``` php - * dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeOptionIsSelected() - */ - public function cantSeeOptionIsSelected($selector, $optionText) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeOptionIsSelected', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the given option is not selected. - * - * ``` php - * dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); - * ?> - * ``` - * - * @param $selector - * @param $optionText - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::dontSeeOptionIsSelected() - */ - public function dontSeeOptionIsSelected($selector, $optionText) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeOptionIsSelected', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Asserts that current page has 404 response status code. - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seePageNotFound() - */ - public function canSeePageNotFound() { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seePageNotFound', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Asserts that current page has 404 response status code. - * @see \Codeception\Lib\InnerBrowser::seePageNotFound() - */ - public function seePageNotFound() { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seePageNotFound', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is equal to value provided. - * - * ```php - * seeResponseCodeIs(200); - * - * // recommended \Codeception\Util\HttpCode - * $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); - * ``` - * - * @param $code - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIs() - */ - public function canSeeResponseCodeIs($code) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIs', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is equal to value provided. - * - * ```php - * seeResponseCodeIs(200); - * - * // recommended \Codeception\Util\HttpCode - * $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); - * ``` - * - * @param $code - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIs() - */ - public function seeResponseCodeIs($code) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResponseCodeIs', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is between a certain range. Between actually means [from <= CODE <= to] - * - * @param $from - * @param $to - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsBetween() - */ - public function canSeeResponseCodeIsBetween($from, $to) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIsBetween', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is between a certain range. Between actually means [from <= CODE <= to] - * - * @param $from - * @param $to - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsBetween() - */ - public function seeResponseCodeIsBetween($from, $to) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResponseCodeIsBetween', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is equal to value provided. - * - * ```php - * dontSeeResponseCodeIs(200); - * - * // recommended \Codeception\Util\HttpCode - * $I->dontSeeResponseCodeIs(\Codeception\Util\HttpCode::OK); - * ``` - * @param $code - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeResponseCodeIs() - */ - public function cantSeeResponseCodeIs($code) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeResponseCodeIs', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that response code is equal to value provided. - * - * ```php - * dontSeeResponseCodeIs(200); - * - * // recommended \Codeception\Util\HttpCode - * $I->dontSeeResponseCodeIs(\Codeception\Util\HttpCode::OK); - * ``` - * @param $code - * @see \Codeception\Lib\InnerBrowser::dontSeeResponseCodeIs() - */ - public function dontSeeResponseCodeIs($code) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeResponseCodeIs', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code 2xx - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsSuccessful() - */ - public function canSeeResponseCodeIsSuccessful() { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIsSuccessful', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code 2xx - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsSuccessful() - */ - public function seeResponseCodeIsSuccessful() { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResponseCodeIsSuccessful', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code 3xx - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsRedirection() - */ - public function canSeeResponseCodeIsRedirection() { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIsRedirection', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code 3xx - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsRedirection() - */ - public function seeResponseCodeIsRedirection() { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResponseCodeIsRedirection', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code is 4xx - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsClientError() - */ - public function canSeeResponseCodeIsClientError() { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIsClientError', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code is 4xx - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsClientError() - */ - public function seeResponseCodeIsClientError() { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResponseCodeIsClientError', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code is 5xx - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsServerError() - */ - public function canSeeResponseCodeIsServerError() { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeResponseCodeIsServerError', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the response code is 5xx - * @see \Codeception\Lib\InnerBrowser::seeResponseCodeIsServerError() - */ - public function seeResponseCodeIsServerError() { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeResponseCodeIsServerError', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title contains the given string. - * - * ``` php - * seeInTitle('Blog - Post #1'); - * ?> - * ``` - * - * @param $title - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::seeInTitle() - */ - public function canSeeInTitle($title) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInTitle', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title contains the given string. - * - * ``` php - * seeInTitle('Blog - Post #1'); - * ?> - * ``` - * - * @param $title - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::seeInTitle() - */ - public function seeInTitle($title) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInTitle', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title does not contain the given string. - * - * @param $title - * - * @return mixed - * Conditional Assertion: Test won't be stopped on fail - * @see \Codeception\Lib\InnerBrowser::dontSeeInTitle() - */ - public function cantSeeInTitle($title) { - return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInTitle', func_get_args())); - } - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Checks that the page title does not contain the given string. - * - * @param $title - * - * @return mixed - * @see \Codeception\Lib\InnerBrowser::dontSeeInTitle() - */ - public function dontSeeInTitle($title) { - return $this->getScenario()->runStep(new \Codeception\Step\Assertion('dontSeeInTitle', func_get_args())); - } - - - /** - * [!] Method is generated. Documentation taken from corresponding module. - * - * Switch to iframe or frame on the page. - * - * Example: - * ``` html - *