@occam-scaly/scaly-cli 0.2.5 → 0.2.7

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.
package/bin/scaly.js CHANGED
@@ -2992,9 +2992,9 @@ async function runDiagnoseApp(rest) {
2992
2992
  }
2993
2993
  if (/User pool .* does not exist/i.test(text)) {
2994
2994
  findings.push({
2995
- code: 'COGNITO_POOL_MISSING',
2995
+ code: 'USER_GROUPS_POOL_MISSING',
2996
2996
  severity: 'medium',
2997
- hint: 'Identity add-on misconfigured; verify user pool id/region.'
2997
+ hint: "User Groups is misconfigured for this account; contact support to repair the account's identity resources."
2998
2998
  });
2999
2999
  }
3000
3000
  if (/ResourceAlreadyExistsException/i.test(text)) {
package/lib/scaly-api.js CHANGED
@@ -254,6 +254,27 @@ const QUERIES = {
254
254
  }
255
255
  }
256
256
  `,
257
+ listAddOnsByType: `
258
+ query ListAddOnsByType($accountId: String!, $type: AddOnTypeEnum!, $take: Int) {
259
+ listAddOns(
260
+ where: {
261
+ AND: [
262
+ { accountId: { equals: $accountId } }
263
+ { type: { equals: $type } }
264
+ { isDeleted: { equals: false } }
265
+ ]
266
+ }
267
+ take: $take
268
+ ) {
269
+ id
270
+ name
271
+ type
272
+ accountId
273
+ status
274
+ addOnCognito { userPoolName }
275
+ }
276
+ }
277
+ `,
257
278
  listStacks: `
258
279
  query ListStacks($take: Int) {
259
280
  listStacks(
@@ -351,6 +372,15 @@ async function findAddOnByName({ name, accountId }) {
351
372
  return data?.listAddOns || [];
352
373
  }
353
374
 
375
+ async function listAddOnsByType({ accountId, type, take = 10 }) {
376
+ const data = await graphqlRequest(QUERIES.listAddOnsByType, {
377
+ accountId,
378
+ type,
379
+ take
380
+ });
381
+ return data?.listAddOns || [];
382
+ }
383
+
354
384
  async function createStack({ accountId, name, size, minIdle }) {
355
385
  const data = await graphqlRequest(MUTATIONS.createStack, {
356
386
  data: {
@@ -419,6 +449,7 @@ module.exports = {
419
449
  findStackByName,
420
450
  findAppByName,
421
451
  findAddOnByName,
452
+ listAddOnsByType,
422
453
  listStacks,
423
454
  listApps,
424
455
  createStack,
@@ -34,6 +34,27 @@ const SENSITIVE_PATH_PATTERNS = [
34
34
  // ssh / aws creds
35
35
  /(^|\/)\.ssh(\/|$)/i,
36
36
  /(^|\/)\.aws(\/|$)/i,
37
+ // common package registry credentials
38
+ /(^|\/)\.npmrc$/i,
39
+ /(^|\/)\.pypirc$/i,
40
+ // docker registry auth
41
+ /(^|\/)\.docker\/config\.json$/i,
42
+ // kubernetes creds
43
+ /(^|\/)\.kube(\/|$)/i,
44
+ /(^|\/)(kubeconfig|.*\.kubeconfig)$/i,
45
+ // terraform state (often contains secrets)
46
+ /(^|\/).*\.tfstate(\.backup)?$/i,
47
+ /(^|\/)terraform\.tfstate(\.backup)?$/i,
48
+ // GCP service account keys / generic credentials files
49
+ /(^|\/)credentials\.json$/i,
50
+ /(^|\/)service-account.*\.json$/i,
51
+ // keystores / pkcs
52
+ /\.p12$/i,
53
+ /\.pfx$/i,
54
+ /\.keystore$/i,
55
+ /\.jks$/i,
56
+ // generic "secrets" files (defense-in-depth; can be overridden)
57
+ /(^|\/).*secrets?.*\.(ya?ml|json)$/i,
37
58
  // private keys
38
59
  /\.pem$/i,
39
60
  /\.key$/i,
package/lib/scaly-plan.js CHANGED
@@ -122,6 +122,17 @@ async function buildPlan({ config, env, appName }) {
122
122
  accountId = await resolveAccountId({ config });
123
123
  }
124
124
 
125
+ const requestedAuthPools = new Set();
126
+ for (const a of filteredApps) {
127
+ const pool = a && a.auth && a.auth.userPool;
128
+ if (typeof pool === 'string' && pool.trim()) requestedAuthPools.add(pool);
129
+ }
130
+ if (requestedAuthPools.size) {
131
+ warnings.push(
132
+ "app.auth.userPool is deprecated and ignored. Scaly uses the account's User Groups automatically; configure only app.auth.groups."
133
+ );
134
+ }
135
+
125
136
  let stackOpId = null;
126
137
  if (!currentStack) {
127
138
  stackOpId = 'op_stack_create';
@@ -258,6 +269,7 @@ async function buildPlan({ config, env, appName }) {
258
269
 
259
270
  if (Array.isArray(config.addons) && config.addons.length) {
260
271
  let addOnReadable = true;
272
+ let existingUserPools = null;
261
273
  for (const addOn of config.addons) {
262
274
  if (!addOn || typeof addOn !== 'object') continue;
263
275
  const apiType = mapAddOnType(addOn.type);
@@ -274,6 +286,30 @@ async function buildPlan({ config, env, appName }) {
274
286
 
275
287
  let currentAddOn = null;
276
288
  try {
289
+ if (apiType === 'COGNITO') {
290
+ try {
291
+ existingUserPools =
292
+ existingUserPools ||
293
+ (await api.listAddOnsByType({
294
+ accountId,
295
+ type: 'COGNITO',
296
+ take: 10
297
+ }));
298
+ } catch {
299
+ existingUserPools = null;
300
+ }
301
+ if (
302
+ Array.isArray(existingUserPools) &&
303
+ existingUserPools.length > 1
304
+ ) {
305
+ const e = new Error(
306
+ `Multiple User Groups pools exist in this account (${existingUserPools.length}). Scaly supports exactly one user pool per account; contact support to repair this account before continuing.`
307
+ );
308
+ e.code = 'USER_GROUPS_POOL_DUPLICATE';
309
+ throw e;
310
+ }
311
+ }
312
+
277
313
  const list = await api.findAddOnByName({ name: addOn.name, accountId });
278
314
  currentAddOn = list?.[0] || null;
279
315
  if (list.length > 1) {
@@ -291,6 +327,40 @@ async function buildPlan({ config, env, appName }) {
291
327
  }
292
328
 
293
329
  if (!currentAddOn) {
330
+ if (apiType === 'COGNITO') {
331
+ const existing =
332
+ Array.isArray(existingUserPools) && existingUserPools.length
333
+ ? existingUserPools.find((p) => p && p.name === 'users') ||
334
+ existingUserPools[0]
335
+ : null;
336
+
337
+ if (existing) {
338
+ warnings.push(
339
+ `User Groups is provisioned automatically per account. Ignoring configured user_groups add-on '${addOn.name}' and using existing '${existing.name}'.`
340
+ );
341
+ ops.push({
342
+ id: `op_addon_noop_${existing.name}`,
343
+ kind: 'addon',
344
+ action: 'noop',
345
+ resource: {
346
+ type: 'addon',
347
+ name: existing.name,
348
+ id: existing.id
349
+ },
350
+ current: pick(existing, ['id', 'name', 'type', 'accountId']),
351
+ desired: { name: existing.name, type: apiType, accountId },
352
+ diff: []
353
+ });
354
+ continue;
355
+ }
356
+
357
+ const e = new Error(
358
+ 'User Groups pool is missing for this account. Scaly provisions it automatically at account creation; contact support to repair this account.'
359
+ );
360
+ e.code = 'USER_GROUPS_POOL_MISSING';
361
+ throw e;
362
+ }
363
+
294
364
  const desired = { name: addOn.name, type: apiType, accountId };
295
365
  if (apiType === 'DATABASE') {
296
366
  const dbType = mapDbEngine(addOn.engine);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@occam-scaly/scaly-cli",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Scaly CLI (auth + project config helpers)",
5
5
  "bin": {
6
6
  "scaly": "./bin/scaly.js"