@freecodecamp/universe-cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +14896 -5077
  2. package/dist/index.js +52 -8
  3. package/package.json +21 -5
package/dist/index.js CHANGED
@@ -53,7 +53,7 @@ var staticSchema = z.object({
53
53
  bucket: z.string().min(1).default("gxy-static-1"),
54
54
  rclone_remote: z.string().min(1).default("gxy-static"),
55
55
  region: z.string().min(1).default("auto")
56
- }).default({});
56
+ }).prefault({});
57
57
  var domainSchema = z.object({
58
58
  production: z.string().min(1),
59
59
  preview: z.string().min(1)
@@ -125,7 +125,19 @@ function loadConfig(options = {}) {
125
125
  throw err;
126
126
  }
127
127
  const parsed = parseYaml(raw);
128
- const yamlValidated = platformSchema.parse(parsed);
128
+ const parseResult = platformSchema.safeParse(parsed);
129
+ if (!parseResult.success) {
130
+ const issues = parseResult.error.issues.map((issue) => {
131
+ const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
132
+ return ` - ${path}: ${issue.message}`;
133
+ }).join("\n");
134
+ throw new ConfigError(
135
+ `platform.yaml is invalid:
136
+ ${issues}
137
+ See STAFF-GUIDE.md for the required format.`
138
+ );
139
+ }
140
+ const yamlValidated = parseResult.data;
129
141
  const envOverrides = readEnvOverrides();
130
142
  const flagOverrides = readFlagOverrides(options.flags);
131
143
  const merged = {
@@ -144,11 +156,13 @@ function loadConfig(options = {}) {
144
156
  import { execSync } from "child_process";
145
157
 
146
158
  // src/output/redact.ts
147
- var AKIA_PATTERN = /AKIA[A-Z0-9]{12,}/g;
159
+ var AWS_KEY_PREFIX_PATTERN = /(?:AKIA|ASIA|AROA|AIDA|ACCA|ANPA|ABIA|AGPA)[A-Z0-9]{12,}/g;
148
160
  var URL_CREDS_PATTERN = /https?:\/\/[^@\s]+@/g;
149
- var CREDENTIAL_CONTEXT_PATTERN = /(?:secret|password|token|key|credential|auth)[=:]\s*([A-Za-z0-9/+=]{21,})/gi;
150
- var HEX_CREDENTIAL_CONTEXT_PATTERN = /(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key)[=:]\s*([a-f0-9]{32,})/gi;
151
- function maskAkia(match) {
161
+ var CREDENTIAL_CONTEXT_PATTERN = /(?:access_key_id|secret_access_key|accessKeyId|secretAccessKey|secret|password|token|key|credential|auth)\s*[=:]\s*([A-Za-z0-9/+=]{21,})/gi;
162
+ var HEX_CREDENTIAL_CONTEXT_PATTERN = /(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key)\s*[=:]\s*([a-f0-9]{32,})/gi;
163
+ var JSON_CREDENTIAL_PATTERN = /"(?:secret|password|token|key|credential|auth|access_key_id|secret_access_key|accessKeyId|secretAccessKey)"\s*:\s*"[^"]+"/gi;
164
+ var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
165
+ function maskAwsKey(match) {
152
166
  return match.slice(0, 4) + "****" + match.slice(-4);
153
167
  }
154
168
  function maskUrlCreds(match) {
@@ -159,7 +173,12 @@ function maskUrlCreds(match) {
159
173
  function redact(value) {
160
174
  let result = value;
161
175
  result = result.replace(URL_CREDS_PATTERN, maskUrlCreds);
162
- result = result.replace(AKIA_PATTERN, maskAkia);
176
+ result = result.replace(AWS_KEY_PREFIX_PATTERN, maskAwsKey);
177
+ result = result.replace(BEARER_PATTERN, "Bearer ****");
178
+ result = result.replace(JSON_CREDENTIAL_PATTERN, (match) => {
179
+ const colonIndex = match.indexOf(":");
180
+ return match.slice(0, colonIndex + 1) + '"****"';
181
+ });
163
182
  result = result.replace(
164
183
  CREDENTIAL_CONTEXT_PATTERN,
165
184
  (_match, _secret, _offset, _full) => {
@@ -181,6 +200,30 @@ function redact(value) {
181
200
  }
182
201
 
183
202
  // src/credentials/resolver.ts
203
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
204
+ function validateEndpoint(endpoint) {
205
+ let url;
206
+ try {
207
+ url = new URL(endpoint);
208
+ } catch {
209
+ throw new CredentialError(`S3_ENDPOINT is not a valid URL: ${endpoint}`);
210
+ }
211
+ if (url.username !== "" || url.password !== "") {
212
+ throw new CredentialError(
213
+ "S3_ENDPOINT must not contain credentials in the URL (user:pass@host). Use S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY instead."
214
+ );
215
+ }
216
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
217
+ throw new CredentialError(
218
+ `S3_ENDPOINT must use http or https, got: ${url.protocol}`
219
+ );
220
+ }
221
+ if (url.protocol === "http:" && !LOCAL_HOSTS.has(url.hostname)) {
222
+ throw new CredentialError(
223
+ `S3_ENDPOINT must use https for non-localhost hosts. Plaintext http is only allowed for localhost/127.0.0.1. Got: ${endpoint}`
224
+ );
225
+ }
226
+ }
184
227
  function tryEnvCredentials() {
185
228
  const key = process.env.S3_ACCESS_KEY_ID;
186
229
  const secret = process.env.S3_SECRET_ACCESS_KEY;
@@ -189,6 +232,7 @@ function tryEnvCredentials() {
189
232
  const present = [key, secret, endpoint].filter(Boolean);
190
233
  if (present.length === 0) return "none";
191
234
  if (present.length < 3) return "partial";
235
+ validateEndpoint(endpoint);
192
236
  const creds = {
193
237
  accessKeyId: key,
194
238
  secretAccessKey: secret,
@@ -872,7 +916,7 @@ async function rollback(options) {
872
916
  }
873
917
 
874
918
  // src/cli.ts
875
- var version = true ? "0.1.0" : "0.0.0";
919
+ var version = true ? "0.3.0" : "0.0.0";
876
920
  function handleActionError(command, json, err) {
877
921
  const ctx = { json, command };
878
922
  const message = err instanceof Error ? err.message : "unknown error";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freecodecamp/universe-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "test": "vitest run",
@@ -27,11 +27,11 @@
27
27
  "dependencies": {
28
28
  "@aws-sdk/client-s3": "^3.1029.0",
29
29
  "@clack/prompts": "^1.2.0",
30
- "cac": "^6.7.14",
30
+ "cac": "^7.0.0",
31
31
  "mrmime": "^2.0.1",
32
- "p-limit": "^6.2.0",
32
+ "p-limit": "^7.3.0",
33
33
  "yaml": "^2.8.3",
34
- "zod": "^3.25.76"
34
+ "zod": "^4.3.6"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@smithy/util-stream": "^4.5.22",
@@ -43,5 +43,21 @@
43
43
  "typescript": "^5.9.3",
44
44
  "vitest": "^3.2.4"
45
45
  },
46
- "packageManager": "pnpm@10.28.0"
46
+ "packageManager": "pnpm@10.33.0",
47
+ "engines": {
48
+ "node": ">=22.11.0"
49
+ },
50
+ "description": "Static site deployment CLI for the freeCodeCamp Universe platform",
51
+ "homepage": "https://github.com/freeCodeCamp-Universe/universe-cli#readme",
52
+ "bugs": {
53
+ "url": "https://github.com/freeCodeCamp-Universe/universe-cli/issues"
54
+ },
55
+ "keywords": [
56
+ "cli",
57
+ "s3",
58
+ "cloudflare-r2",
59
+ "static-site",
60
+ "deployment",
61
+ "freecodecamp"
62
+ ]
47
63
  }