watch-tail 0.2.1 → 0.3.1

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 (74) hide show
  1. package/README.md +30 -0
  2. package/build/client/_app/immutable/assets/0.CocWI5K4.css +2 -0
  3. package/build/client/_app/immutable/assets/0.CocWI5K4.css.br +0 -0
  4. package/build/client/_app/immutable/assets/0.CocWI5K4.css.gz +0 -0
  5. package/build/client/_app/immutable/chunks/{BiIbMEwd.js → 05WsiB4A.js} +1 -1
  6. package/build/client/_app/immutable/chunks/05WsiB4A.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/05WsiB4A.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/{DwpzNa8H.js → DyIYFD7i.js} +1 -1
  9. package/build/client/_app/immutable/chunks/DyIYFD7i.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/DyIYFD7i.js.gz +0 -0
  11. package/build/client/_app/immutable/entry/{app.B-LBdafb.js → app.D0_YFW4X.js} +2 -2
  12. package/build/client/_app/immutable/entry/app.D0_YFW4X.js.br +0 -0
  13. package/build/client/_app/immutable/entry/app.D0_YFW4X.js.gz +0 -0
  14. package/build/client/_app/immutable/entry/start.D1rrHrg2.js +1 -0
  15. package/build/client/_app/immutable/entry/start.D1rrHrg2.js.br +0 -0
  16. package/build/client/_app/immutable/entry/start.D1rrHrg2.js.gz +0 -0
  17. package/build/client/_app/immutable/nodes/{1.dLH1x-yV.js → 1.D1vhnOgO.js} +1 -1
  18. package/build/client/_app/immutable/nodes/1.D1vhnOgO.js.br +0 -0
  19. package/build/client/_app/immutable/nodes/1.D1vhnOgO.js.gz +0 -0
  20. package/build/client/_app/immutable/nodes/{2.PY6hzMBU.js → 2.BPdL6B7F.js} +1 -1
  21. package/build/client/_app/immutable/nodes/2.BPdL6B7F.js.br +0 -0
  22. package/build/client/_app/immutable/nodes/2.BPdL6B7F.js.gz +0 -0
  23. package/build/client/_app/version.json +1 -1
  24. package/build/client/_app/version.json.br +0 -0
  25. package/build/client/_app/version.json.gz +0 -0
  26. package/build/handler.js +4 -4
  27. package/build/index.js +4 -4
  28. package/build/server/chunks/chunks/{internal.js-Bp_eP8ac.js → internal.js-B-DZpK-L.js} +2 -2
  29. package/build/server/chunks/chunks/{internal.js-Bp_eP8ac.js.map → internal.js-B-DZpK-L.js.map} +1 -1
  30. package/build/server/chunks/chunks/{state.js-dp_P2xFv.js → state.js-V2NYGhy_.js} +2 -2
  31. package/build/server/chunks/chunks/{state.js-dp_P2xFv.js.map → state.js-V2NYGhy_.js.map} +1 -1
  32. package/build/server/chunks/entries/fallbacks/{error.svelte.js-Cr_jQ6By.js → error.svelte.js-CAasf3PF.js} +3 -3
  33. package/build/server/chunks/entries/fallbacks/{error.svelte.js-Cr_jQ6By.js.map → error.svelte.js-CAasf3PF.js.map} +1 -1
  34. package/build/server/chunks/entries/pages/{_page.svelte.js-BYE11gdZ.js → _page.svelte.js-DzAAqWz2.js} +3 -3
  35. package/build/server/chunks/entries/pages/{_page.svelte.js-BYE11gdZ.js.map → _page.svelte.js-DzAAqWz2.js.map} +1 -1
  36. package/build/server/chunks/{handler-BTS3V5-R.js → handler-CuWqZDuC.js} +3 -3
  37. package/build/server/chunks/{handler-BTS3V5-R.js.map → handler-CuWqZDuC.js.map} +1 -1
  38. package/build/server/chunks/{index.js-CqJBHkzQ.js → index.js-NHTL74z2.js} +2 -2
  39. package/build/server/chunks/{index.js-CqJBHkzQ.js.map → index.js-NHTL74z2.js.map} +1 -1
  40. package/build/server/chunks/{manifest.js-DjD36Niy.js → manifest.js-8g4gjxwf.js} +5 -5
  41. package/build/server/chunks/{manifest.js-DjD36Niy.js.map → manifest.js-8g4gjxwf.js.map} +1 -1
  42. package/build/server/chunks/nodes/{0.js-Dl0J9d3p.js → 0.js-Bg4_hDjk.js} +3 -3
  43. package/build/server/chunks/nodes/{0.js-Dl0J9d3p.js.map → 0.js-Bg4_hDjk.js.map} +1 -1
  44. package/build/server/chunks/nodes/{1.js-rafEmQ-L.js → 1.js-CDSk3TvW.js} +3 -3
  45. package/build/server/chunks/nodes/{1.js-rafEmQ-L.js.map → 1.js-CDSk3TvW.js.map} +1 -1
  46. package/build/server/chunks/nodes/2.js-BVhBcWrN.js +9 -0
  47. package/build/server/chunks/nodes/{2.js-Dc-ZnMEK.js.map → 2.js-BVhBcWrN.js.map} +1 -1
  48. package/dist/cli/index.js +91 -3
  49. package/dist/cli/preflight.js +73 -0
  50. package/dist/cli/ui.js +8 -1
  51. package/dist/lib/cli/aws.js +57 -1
  52. package/dist/lib/cli/credentials.js +163 -0
  53. package/dist/lib/types.js +5 -0
  54. package/package.json +1 -1
  55. package/build/client/_app/immutable/assets/0.B1NsRRrX.css +0 -2
  56. package/build/client/_app/immutable/assets/0.B1NsRRrX.css.br +0 -0
  57. package/build/client/_app/immutable/assets/0.B1NsRRrX.css.gz +0 -0
  58. package/build/client/_app/immutable/chunks/BiIbMEwd.js.br +0 -0
  59. package/build/client/_app/immutable/chunks/BiIbMEwd.js.gz +0 -0
  60. package/build/client/_app/immutable/chunks/DwpzNa8H.js.br +0 -0
  61. package/build/client/_app/immutable/chunks/DwpzNa8H.js.gz +0 -0
  62. package/build/client/_app/immutable/entry/app.B-LBdafb.js.br +0 -0
  63. package/build/client/_app/immutable/entry/app.B-LBdafb.js.gz +0 -0
  64. package/build/client/_app/immutable/entry/start.DO6ike1m.js +0 -1
  65. package/build/client/_app/immutable/entry/start.DO6ike1m.js.br +0 -0
  66. package/build/client/_app/immutable/entry/start.DO6ike1m.js.gz +0 -0
  67. package/build/client/_app/immutable/nodes/1.dLH1x-yV.js.br +0 -1
  68. package/build/client/_app/immutable/nodes/1.dLH1x-yV.js.gz +0 -0
  69. package/build/client/_app/immutable/nodes/2.PY6hzMBU.js.br +0 -0
  70. package/build/client/_app/immutable/nodes/2.PY6hzMBU.js.gz +0 -0
  71. package/build/server/chunks/nodes/2.js-Dc-ZnMEK.js +0 -9
  72. /package/build/client/_app/immutable/nodes/{0.D5bYt7iX.js → 0.DHBUWbOo.js} +0 -0
  73. /package/build/client/_app/immutable/nodes/{0.D5bYt7iX.js.br → 0.DHBUWbOo.js.br} +0 -0
  74. /package/build/client/_app/immutable/nodes/{0.D5bYt7iX.js.gz → 0.DHBUWbOo.js.gz} +0 -0
@@ -10,11 +10,11 @@ return {
10
10
  assets: new Set(["robots.txt"]),
11
11
  mimeTypes: {".txt":"text/plain"},
12
12
  _: {
13
- client: {start:"_app/immutable/entry/start.DO6ike1m.js",app:"_app/immutable/entry/app.B-LBdafb.js",imports:["_app/immutable/entry/start.DO6ike1m.js","_app/immutable/chunks/BiIbMEwd.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/entry/app.B-LBdafb.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/chunks/xihTtKlq.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
13
+ client: {start:"_app/immutable/entry/start.D1rrHrg2.js",app:"_app/immutable/entry/app.D0_YFW4X.js",imports:["_app/immutable/entry/start.D1rrHrg2.js","_app/immutable/chunks/05WsiB4A.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/entry/app.D0_YFW4X.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/chunks/xihTtKlq.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
14
14
  nodes: [
15
- __memo(() => import('./nodes/0.js-Dl0J9d3p.js')),
16
- __memo(() => import('./nodes/1.js-rafEmQ-L.js')),
17
- __memo(() => import('./nodes/2.js-Dc-ZnMEK.js'))
15
+ __memo(() => import('./nodes/0.js-Bg4_hDjk.js')),
16
+ __memo(() => import('./nodes/1.js-CDSk3TvW.js')),
17
+ __memo(() => import('./nodes/2.js-BVhBcWrN.js'))
18
18
  ],
19
19
  remotes: {
20
20
 
@@ -67,4 +67,4 @@ return {
67
67
  })();
68
68
 
69
69
  export { manifest as m };
70
- //# sourceMappingURL=manifest.js-DjD36Niy.js.map
70
+ //# sourceMappingURL=manifest.js-8g4gjxwf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js-DjD36Niy.js","sources":["../../../.svelte-kit/output/server/manifest.js"],"sourcesContent":["export const manifest = (() => {\nfunction __memo(fn) {\n\tlet value;\n\treturn () => value ??= (value = fn());\n}\n\nreturn {\n\tappDir: \"_app\",\n\tappPath: \"_app\",\n\tassets: new Set([\"robots.txt\"]),\n\tmimeTypes: {\".txt\":\"text/plain\"},\n\t_: {\n\t\tclient: {start:\"_app/immutable/entry/start.DO6ike1m.js\",app:\"_app/immutable/entry/app.B-LBdafb.js\",imports:[\"_app/immutable/entry/start.DO6ike1m.js\",\"_app/immutable/chunks/BiIbMEwd.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/entry/app.B-LBdafb.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/xihTtKlq.js\"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},\n\t\tnodes: [\n\t\t\t__memo(() => import('./nodes/0.js')),\n\t\t\t__memo(() => import('./nodes/1.js')),\n\t\t\t__memo(() => import('./nodes/2.js'))\n\t\t],\n\t\tremotes: {\n\t\t\t\n\t\t},\n\t\troutes: [\n\t\t\t{\n\t\t\t\tid: \"/\",\n\t\t\t\tpattern: /^\\/$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: { layouts: [0,], errors: [1,], leaf: 2 },\n\t\t\t\tendpoint: null\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/health\",\n\t\t\t\tpattern: /^\\/api\\/health\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/health/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/log-groups\",\n\t\t\t\tpattern: /^\\/api\\/log-groups\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/log-groups/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/regions\",\n\t\t\t\tpattern: /^\\/api\\/regions\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/regions/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/stream\",\n\t\t\t\tpattern: /^\\/api\\/stream\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/stream/_server.ts.js'))\n\t\t\t}\n\t\t],\n\t\tprerendered_routes: new Set([]),\n\t\tmatchers: async () => {\n\t\t\t\n\t\t\treturn { };\n\t\t},\n\t\tserver_assets: {}\n\t}\n}\n})();\n"],"names":[],"mappings":"AAAY,MAAC,QAAQ,GAAG,CAAC,MAAM;AAC/B,SAAS,MAAM,CAAC,EAAE,EAAE;AACpB,CAAC,IAAI,KAAK;AACV,CAAC,OAAO,MAAM,KAAK,MAAM,KAAK,GAAG,EAAE,EAAE,CAAC;AACtC;;AAEA,OAAO;AACP,CAAC,MAAM,EAAE,MAAM;AACf,CAAC,OAAO,EAAE,MAAM;AAChB,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;AAChC,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;AACjC,CAAC,CAAC,EAAE;AACJ,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,wCAAwC,CAAC,GAAG,CAAC,sCAAsC,CAAC,OAAO,CAAC,CAAC,wCAAwC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,sCAAsC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC;AACrY,EAAE,KAAK,EAAE;AACT,GAAG,MAAM,CAAC,MAAM,OAAO,0BAAc,CAAC,CAAC;AACvC,GAAG,MAAM,CAAC,MAAM,OAAO,0BAAc,CAAC,CAAC;AACvC,GAAG,MAAM,CAAC,MAAM,OAAO,0BAAc,CAAC;AACtC,GAAG;AACH,EAAE,OAAO,EAAE;AACX;AACA,GAAG;AACH,EAAE,MAAM,EAAE;AACV,GAAG;AACH,IAAI,EAAE,EAAE,GAAG;AACX,IAAI,OAAO,EAAE,MAAM;AACnB,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;AAClD,IAAI,QAAQ,EAAE;AACd,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,aAAa;AACrB,IAAI,OAAO,EAAE,oBAAoB;AACjC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,0DAA8C,CAAC;AACjF,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,iBAAiB;AACzB,IAAI,OAAO,EAAE,wBAAwB;AACrC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,8DAAkD,CAAC;AACrF,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,cAAc;AACtB,IAAI,OAAO,EAAE,qBAAqB;AAClC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,2DAA+C,CAAC;AAClF,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,aAAa;AACrB,IAAI,OAAO,EAAE,oBAAoB;AACjC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,0DAA8C,CAAC;AACjF;AACA,GAAG;AACH,EAAE,kBAAkB,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;AACjC,EAAE,QAAQ,EAAE,YAAY;AACxB;AACA,GAAG,OAAO,IAAI;AACd,EAAE,CAAC;AACH,EAAE,aAAa,EAAE;AACjB;AACA;AACA,CAAC;;"}
1
+ {"version":3,"file":"manifest.js-8g4gjxwf.js","sources":["../../../.svelte-kit/output/server/manifest.js"],"sourcesContent":["export const manifest = (() => {\nfunction __memo(fn) {\n\tlet value;\n\treturn () => value ??= (value = fn());\n}\n\nreturn {\n\tappDir: \"_app\",\n\tappPath: \"_app\",\n\tassets: new Set([\"robots.txt\"]),\n\tmimeTypes: {\".txt\":\"text/plain\"},\n\t_: {\n\t\tclient: {start:\"_app/immutable/entry/start.D1rrHrg2.js\",app:\"_app/immutable/entry/app.D0_YFW4X.js\",imports:[\"_app/immutable/entry/start.D1rrHrg2.js\",\"_app/immutable/chunks/05WsiB4A.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/entry/app.D0_YFW4X.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/xihTtKlq.js\"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},\n\t\tnodes: [\n\t\t\t__memo(() => import('./nodes/0.js')),\n\t\t\t__memo(() => import('./nodes/1.js')),\n\t\t\t__memo(() => import('./nodes/2.js'))\n\t\t],\n\t\tremotes: {\n\t\t\t\n\t\t},\n\t\troutes: [\n\t\t\t{\n\t\t\t\tid: \"/\",\n\t\t\t\tpattern: /^\\/$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: { layouts: [0,], errors: [1,], leaf: 2 },\n\t\t\t\tendpoint: null\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/health\",\n\t\t\t\tpattern: /^\\/api\\/health\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/health/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/log-groups\",\n\t\t\t\tpattern: /^\\/api\\/log-groups\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/log-groups/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/regions\",\n\t\t\t\tpattern: /^\\/api\\/regions\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/regions/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/stream\",\n\t\t\t\tpattern: /^\\/api\\/stream\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/stream/_server.ts.js'))\n\t\t\t}\n\t\t],\n\t\tprerendered_routes: new Set([]),\n\t\tmatchers: async () => {\n\t\t\t\n\t\t\treturn { };\n\t\t},\n\t\tserver_assets: {}\n\t}\n}\n})();\n"],"names":[],"mappings":"AAAY,MAAC,QAAQ,GAAG,CAAC,MAAM;AAC/B,SAAS,MAAM,CAAC,EAAE,EAAE;AACpB,CAAC,IAAI,KAAK;AACV,CAAC,OAAO,MAAM,KAAK,MAAM,KAAK,GAAG,EAAE,EAAE,CAAC;AACtC;;AAEA,OAAO;AACP,CAAC,MAAM,EAAE,MAAM;AACf,CAAC,OAAO,EAAE,MAAM;AAChB,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;AAChC,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC;AACjC,CAAC,CAAC,EAAE;AACJ,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,wCAAwC,CAAC,GAAG,CAAC,sCAAsC,CAAC,OAAO,CAAC,CAAC,wCAAwC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,sCAAsC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC;AACrY,EAAE,KAAK,EAAE;AACT,GAAG,MAAM,CAAC,MAAM,OAAO,0BAAc,CAAC,CAAC;AACvC,GAAG,MAAM,CAAC,MAAM,OAAO,0BAAc,CAAC,CAAC;AACvC,GAAG,MAAM,CAAC,MAAM,OAAO,0BAAc,CAAC;AACtC,GAAG;AACH,EAAE,OAAO,EAAE;AACX;AACA,GAAG;AACH,EAAE,MAAM,EAAE;AACV,GAAG;AACH,IAAI,EAAE,EAAE,GAAG;AACX,IAAI,OAAO,EAAE,MAAM;AACnB,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;AAClD,IAAI,QAAQ,EAAE;AACd,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,aAAa;AACrB,IAAI,OAAO,EAAE,oBAAoB;AACjC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,0DAA8C,CAAC;AACjF,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,iBAAiB;AACzB,IAAI,OAAO,EAAE,wBAAwB;AACrC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,8DAAkD,CAAC;AACrF,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,cAAc;AACtB,IAAI,OAAO,EAAE,qBAAqB;AAClC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,2DAA+C,CAAC;AAClF,IAAI;AACJ,GAAG;AACH,IAAI,EAAE,EAAE,aAAa;AACrB,IAAI,OAAO,EAAE,oBAAoB;AACjC,IAAI,MAAM,EAAE,EAAE;AACd,IAAI,IAAI,EAAE,IAAI;AACd,IAAI,QAAQ,EAAE,MAAM,CAAC,MAAM,OAAO,0DAA8C,CAAC;AACjF;AACA,GAAG;AACH,EAAE,kBAAkB,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;AACjC,EAAE,QAAQ,EAAE,YAAY;AACxB;AACA,GAAG,OAAO,IAAI;AACd,EAAE,CAAC;AACH,EAAE,aAAa,EAAE;AACjB;AACA;AACA,CAAC;;"}
@@ -1,9 +1,9 @@
1
1
  const index = 0;
2
2
  let component_cache;
3
3
  const component = async () => component_cache ??= (await import('../entries/pages/_layout.svelte.js-Bx7lXkAi.js')).default;
4
- const imports = ["_app/immutable/nodes/0.D5bYt7iX.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/chunks/xihTtKlq.js"];
5
- const stylesheets = ["_app/immutable/assets/0.B1NsRRrX.css"];
4
+ const imports = ["_app/immutable/nodes/0.DHBUWbOo.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/chunks/xihTtKlq.js"];
5
+ const stylesheets = ["_app/immutable/assets/0.CocWI5K4.css"];
6
6
  const fonts = [];
7
7
 
8
8
  export { component, fonts, imports, index, stylesheets };
9
- //# sourceMappingURL=0.js-Dl0J9d3p.js.map
9
+ //# sourceMappingURL=0.js-Bg4_hDjk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"0.js-Dl0J9d3p.js","sources":["../../../../.svelte-kit/output/server/nodes/0.js"],"sourcesContent":["\n\nexport const index = 0;\nlet component_cache;\nexport const component = async () => component_cache ??= (await import('../entries/pages/_layout.svelte.js')).default;\nexport const imports = [\"_app/immutable/nodes/0.D5bYt7iX.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/xihTtKlq.js\"];\nexport const stylesheets = [\"_app/immutable/assets/0.B1NsRRrX.css\"];\nexport const fonts = [];\n"],"names":[],"mappings":"AAEY,MAAC,KAAK,GAAG;AACrB,IAAI,eAAe;AACP,MAAC,SAAS,GAAG,YAAY,eAAe,KAAK,CAAC,MAAM,OAAO,gDAAoC,CAAC,EAAE;AAClG,MAAC,OAAO,GAAG,CAAC,oCAAoC,CAAC,mCAAmC,CAAC,mCAAmC;AACxH,MAAC,WAAW,GAAG,CAAC,sCAAsC;AACtD,MAAC,KAAK,GAAG;;"}
1
+ {"version":3,"file":"0.js-Bg4_hDjk.js","sources":["../../../../.svelte-kit/output/server/nodes/0.js"],"sourcesContent":["\n\nexport const index = 0;\nlet component_cache;\nexport const component = async () => component_cache ??= (await import('../entries/pages/_layout.svelte.js')).default;\nexport const imports = [\"_app/immutable/nodes/0.DHBUWbOo.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/xihTtKlq.js\"];\nexport const stylesheets = [\"_app/immutable/assets/0.CocWI5K4.css\"];\nexport const fonts = [];\n"],"names":[],"mappings":"AAEY,MAAC,KAAK,GAAG;AACrB,IAAI,eAAe;AACP,MAAC,SAAS,GAAG,YAAY,eAAe,KAAK,CAAC,MAAM,OAAO,gDAAoC,CAAC,EAAE;AAClG,MAAC,OAAO,GAAG,CAAC,oCAAoC,CAAC,mCAAmC,CAAC,mCAAmC;AACxH,MAAC,WAAW,GAAG,CAAC,sCAAsC;AACtD,MAAC,KAAK,GAAG;;"}
@@ -1,9 +1,9 @@
1
1
  const index = 1;
2
2
  let component_cache;
3
- const component = async () => component_cache ??= (await import('../entries/fallbacks/error.svelte.js-Cr_jQ6By.js')).default;
4
- const imports = ["_app/immutable/nodes/1.dLH1x-yV.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/chunks/xihTtKlq.js","_app/immutable/chunks/DwpzNa8H.js","_app/immutable/chunks/BiIbMEwd.js"];
3
+ const component = async () => component_cache ??= (await import('../entries/fallbacks/error.svelte.js-CAasf3PF.js')).default;
4
+ const imports = ["_app/immutable/nodes/1.D1vhnOgO.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/chunks/xihTtKlq.js","_app/immutable/chunks/DyIYFD7i.js","_app/immutable/chunks/05WsiB4A.js"];
5
5
  const stylesheets = [];
6
6
  const fonts = [];
7
7
 
8
8
  export { component, fonts, imports, index, stylesheets };
9
- //# sourceMappingURL=1.js-rafEmQ-L.js.map
9
+ //# sourceMappingURL=1.js-CDSk3TvW.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"1.js-rafEmQ-L.js","sources":["../../../../.svelte-kit/output/server/nodes/1.js"],"sourcesContent":["\n\nexport const index = 1;\nlet component_cache;\nexport const component = async () => component_cache ??= (await import('../entries/fallbacks/error.svelte.js')).default;\nexport const imports = [\"_app/immutable/nodes/1.dLH1x-yV.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/xihTtKlq.js\",\"_app/immutable/chunks/DwpzNa8H.js\",\"_app/immutable/chunks/BiIbMEwd.js\"];\nexport const stylesheets = [];\nexport const fonts = [];\n"],"names":[],"mappings":"AAEY,MAAC,KAAK,GAAG;AACrB,IAAI,eAAe;AACP,MAAC,SAAS,GAAG,YAAY,eAAe,KAAK,CAAC,MAAM,OAAO,kDAAsC,CAAC,EAAE;AACpG,MAAC,OAAO,GAAG,CAAC,oCAAoC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC;AAChM,MAAC,WAAW,GAAG;AACf,MAAC,KAAK,GAAG;;"}
1
+ {"version":3,"file":"1.js-CDSk3TvW.js","sources":["../../../../.svelte-kit/output/server/nodes/1.js"],"sourcesContent":["\n\nexport const index = 1;\nlet component_cache;\nexport const component = async () => component_cache ??= (await import('../entries/fallbacks/error.svelte.js')).default;\nexport const imports = [\"_app/immutable/nodes/1.D1vhnOgO.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/xihTtKlq.js\",\"_app/immutable/chunks/DyIYFD7i.js\",\"_app/immutable/chunks/05WsiB4A.js\"];\nexport const stylesheets = [];\nexport const fonts = [];\n"],"names":[],"mappings":"AAEY,MAAC,KAAK,GAAG;AACrB,IAAI,eAAe;AACP,MAAC,SAAS,GAAG,YAAY,eAAe,KAAK,CAAC,MAAM,OAAO,kDAAsC,CAAC,EAAE;AACpG,MAAC,OAAO,GAAG,CAAC,oCAAoC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC;AAChM,MAAC,WAAW,GAAG;AACf,MAAC,KAAK,GAAG;;"}
@@ -0,0 +1,9 @@
1
+ const index = 2;
2
+ let component_cache;
3
+ const component = async () => component_cache ??= (await import('../entries/pages/_page.svelte.js-DzAAqWz2.js')).default;
4
+ const imports = ["_app/immutable/nodes/2.BPdL6B7F.js","_app/immutable/chunks/ZAD2y5xJ.js","_app/immutable/chunks/05WsiB4A.js","_app/immutable/chunks/xihTtKlq.js","_app/immutable/chunks/DyIYFD7i.js"];
5
+ const stylesheets = [];
6
+ const fonts = [];
7
+
8
+ export { component, fonts, imports, index, stylesheets };
9
+ //# sourceMappingURL=2.js-BVhBcWrN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"2.js-Dc-ZnMEK.js","sources":["../../../../.svelte-kit/output/server/nodes/2.js"],"sourcesContent":["\n\nexport const index = 2;\nlet component_cache;\nexport const component = async () => component_cache ??= (await import('../entries/pages/_page.svelte.js')).default;\nexport const imports = [\"_app/immutable/nodes/2.PY6hzMBU.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/BiIbMEwd.js\",\"_app/immutable/chunks/xihTtKlq.js\",\"_app/immutable/chunks/DwpzNa8H.js\"];\nexport const stylesheets = [];\nexport const fonts = [];\n"],"names":[],"mappings":"AAEY,MAAC,KAAK,GAAG;AACrB,IAAI,eAAe;AACP,MAAC,SAAS,GAAG,YAAY,eAAe,KAAK,CAAC,MAAM,OAAO,8CAAkC,CAAC,EAAE;AAChG,MAAC,OAAO,GAAG,CAAC,oCAAoC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC;AAChM,MAAC,WAAW,GAAG;AACf,MAAC,KAAK,GAAG;;"}
1
+ {"version":3,"file":"2.js-BVhBcWrN.js","sources":["../../../../.svelte-kit/output/server/nodes/2.js"],"sourcesContent":["\n\nexport const index = 2;\nlet component_cache;\nexport const component = async () => component_cache ??= (await import('../entries/pages/_page.svelte.js')).default;\nexport const imports = [\"_app/immutable/nodes/2.BPdL6B7F.js\",\"_app/immutable/chunks/ZAD2y5xJ.js\",\"_app/immutable/chunks/05WsiB4A.js\",\"_app/immutable/chunks/xihTtKlq.js\",\"_app/immutable/chunks/DyIYFD7i.js\"];\nexport const stylesheets = [];\nexport const fonts = [];\n"],"names":[],"mappings":"AAEY,MAAC,KAAK,GAAG;AACrB,IAAI,eAAe;AACP,MAAC,SAAS,GAAG,YAAY,eAAe,KAAK,CAAC,MAAM,OAAO,8CAAkC,CAAC,EAAE;AAChG,MAAC,OAAO,GAAG,CAAC,oCAAoC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC,CAAC,mCAAmC;AAChM,MAAC,WAAW,GAAG;AACf,MAAC,KAAK,GAAG;;"}
package/dist/cli/index.js CHANGED
@@ -10,10 +10,12 @@
10
10
  import { spawn } from 'node:child_process';
11
11
  import { readFileSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
- import { buildChildEnv, describeChildEnv, isEmulatorEndpoint, isLocalEnvPresent, readConfigText, readProfiles, resolveRunRegion, signalExitCode, } from "../lib/cli/aws.js";
13
+ import { buildChildEnv, describeChildEnv, isEmulatorEndpoint, isLocalEnvPresent, readConfigText, readCredentialsText, readLocalEnvValues, readProfiles, resolveRunRegion, signalExitCode, suppressLocalEnvValues, } from "../lib/cli/aws.js";
14
+ import { classifyCredentialFailure, describeStyle, isCredentialFailure, loginAdvice, readProfileStyle, } from "../lib/cli/credentials.js";
14
15
  import { handleCompletion } from "./completions.js";
15
16
  import { isLoopbackHost, parseCliArgs, usageText } from "./options.js";
16
17
  import { appRootFromHere, healthUrl, openBrowser as openBrowserDefault, startServer, stopServer, uiUrl, waitForHealth, } from "./server.js";
18
+ import { isHeadless, probeCredentials as probeCredentialsDefault, runLogin as runLoginDefault, } from "./preflight.js";
17
19
  import { createUi, isInteractive } from "./ui.js";
18
20
  /** Reads the version from the package manifest next to the app root. */
19
21
  export function readVersion(appRoot) {
@@ -68,12 +70,21 @@ function defaultIo() {
68
70
  appRoot,
69
71
  version: readVersion(appRoot),
70
72
  openBrowser: (url) => openBrowserDefault(url),
73
+ readCredentialsText: () => readCredentialsText(),
74
+ readLocalEnvValues: () => (appRoot === null ? {} : readLocalEnvValues(appRoot)),
75
+ probeCredentials: (input) => probeCredentialsDefault(input),
76
+ runLogin: (command) => runLoginDefault(command),
71
77
  waitForHealth,
72
78
  startServerImpl: startServer,
73
79
  spawnImpl: spawn,
74
80
  waitForStop,
75
81
  };
76
82
  }
83
+ /** `AWS_PROFILE` from the environment, trimmed, or `null`. */
84
+ export function ambientProfile(env) {
85
+ const value = env.AWS_PROFILE?.trim();
86
+ return value !== undefined && value.length > 0 ? value : null;
87
+ }
77
88
  /** Region for this run: explicit flag, then the shell, then the profile. */
78
89
  export function resolveCliRegion(options, io) {
79
90
  const resolved = resolveRunRegion({
@@ -90,6 +101,60 @@ export function resolveCliRegion(options, io) {
90
101
  return 'us-east-1';
91
102
  return null;
92
103
  }
104
+ /**
105
+ * Asks the running app for one log group and, when AWS refuses because of
106
+ * credentials, offers to run the login command that profile needs.
107
+ *
108
+ * Emulator runs are skipped: `--floci` supplies throwaway keys on purpose.
109
+ */
110
+ async function ensureCredentials(input) {
111
+ const { options, io, ui, url, region } = input;
112
+ if (options.endpoint !== null)
113
+ return;
114
+ const probe = await io.probeCredentials({ baseUrl: url, region });
115
+ if (probe.ok)
116
+ return;
117
+ if (!probe.credentialProblem || !isCredentialFailure(probe.code, probe.message)) {
118
+ ui.warn(`could not list log groups: ${probe.message}`);
119
+ return;
120
+ }
121
+ // The flag wins, then the ambient AWS_PROFILE: both decide which login
122
+ // command can actually repair the failure.
123
+ const profile = options.profile ?? ambientProfile(io.env) ?? 'default';
124
+ const style = readProfileStyle(io.readConfigText(), io.readCredentialsText(), profile);
125
+ const failure = classifyCredentialFailure(probe.message, probe.code);
126
+ const advice = loginAdvice({
127
+ style,
128
+ failure,
129
+ profile,
130
+ remote: isHeadless(io.env),
131
+ });
132
+ ui.warn(`AWS credentials are not usable: ${probe.message}`);
133
+ if (advice === null) {
134
+ ui.info(style === 'static'
135
+ ? `the ${describeStyle(style)} in profile "${profile}" look wrong - check them with \`aws configure --profile ${profile}\``
136
+ : `no login command applies to profile "${profile}" (${describeStyle(style)})`);
137
+ return;
138
+ }
139
+ const label = `aws ${advice.command.join(' ')}`;
140
+ const accepted = await ui.confirm(`${advice.hint}. Run \`${label}\` now?`);
141
+ if (!accepted) {
142
+ ui.info(`run it yourself, then press Refresh in the UI: ${label}`);
143
+ return;
144
+ }
145
+ ui.info(`running ${label}`);
146
+ const code = await io.runLogin(advice.command);
147
+ if (code !== 0) {
148
+ ui.warn(`${label} exited with code ${code}`);
149
+ ui.info(`log in another terminal, then press Refresh in the UI: ${label}`);
150
+ return;
151
+ }
152
+ const retry = await io.probeCredentials({ baseUrl: url, region });
153
+ if (retry.ok)
154
+ ui.info('signed in - credentials work now');
155
+ else
156
+ ui.warn(`still failing after login: ${retry.message}`);
157
+ }
93
158
  /** Runs the CLI and returns its exit code. */
94
159
  export async function run(argv, overrides = {}) {
95
160
  const io = { ...defaultIo(), ...overrides };
@@ -123,11 +188,28 @@ export async function run(argv, overrides = {}) {
123
188
  return 0;
124
189
  }
125
190
  const region = resolveCliRegion(options, io);
191
+ // `--profile` wins, then the ambient AWS_PROFILE: the credential check and
192
+ // the login advice both need to know which profile is actually in play.
193
+ const profile = options.profile ?? ambientProfile(io.env);
194
+ let baseEnv = io.env;
195
+ let suppressedLocalEnv = [];
196
+ if (options.endpoint === null) {
197
+ // The CLI is real-AWS-by-default, so a dev `.env.local` next to the app
198
+ // (which exists in this repository, and in any checkout of it) must not
199
+ // silently redirect the run at floci. `--floci` asks for that explicitly.
200
+ const suppressed = suppressLocalEnvValues({
201
+ env: io.env,
202
+ values: io.readLocalEnvValues(),
203
+ });
204
+ baseEnv = suppressed.env;
205
+ suppressedLocalEnv = suppressed.suppressed;
206
+ }
126
207
  const childEnv = buildChildEnv({
127
- base: io.env,
208
+ base: baseEnv,
128
209
  profile: options.profile,
129
210
  region,
130
211
  endpoint: options.endpoint,
212
+ clearStaticKeys: options.profile === null && profile !== null,
131
213
  });
132
214
  if (options.print) {
133
215
  for (const line of describeChildEnv(childEnv))
@@ -152,7 +234,10 @@ export async function run(argv, overrides = {}) {
152
234
  if (!isLoopbackHost(options.host)) {
153
235
  ui.warn(`binding ${options.host}: the app has no authentication and uses your AWS access, so anyone who can reach it can read your logs`);
154
236
  }
155
- if (isLocalEnvPresent(io.appRoot) && options.profile !== null) {
237
+ if (suppressedLocalEnv.length > 0) {
238
+ ui.warn(`ignoring the emulator settings in .env.local (${suppressedLocalEnv.join(', ')}) - pass --floci to use them`);
239
+ }
240
+ else if (isLocalEnvPresent(io.appRoot) && options.profile !== null) {
156
241
  ui.warn('a .env.local with local emulator settings was found; it is ignored for this run');
157
242
  }
158
243
  ui.startSpinner('starting the local UI...');
@@ -170,6 +255,9 @@ export async function run(argv, overrides = {}) {
170
255
  return 1;
171
256
  }
172
257
  ui.stopSpinner(`listening on ${url}`);
258
+ // Before sending anyone to a UI that cannot load logs, check that AWS will
259
+ // actually answer, and offer the login that fixes it.
260
+ await ensureCredentials({ options, io, ui, url, region });
173
261
  if (options.open)
174
262
  io.openBrowser(url);
175
263
  ui.outro(`${url} (Ctrl+C to stop)`);
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Startup credential check for the CLI.
3
+ *
4
+ * Rather than resolving credentials twice, the CLI asks the running app: the
5
+ * `/api/log-groups` route uses the same SDK client and the same environment, and
6
+ * it already maps provider failures onto codes such as `missing-credentials`. If
7
+ * that comes back as a credential failure, {@link ./credentials.ts} decides which
8
+ * login command repairs it, and the CLI can run it and retry.
9
+ */
10
+ import { spawn } from 'node:child_process';
11
+ /**
12
+ * Asks the app whether it can list log groups.
13
+ *
14
+ * Only the first error is reported: with no credentials every region fails the
15
+ * same way, and the point is to spot it early, not to be exhaustive.
16
+ */
17
+ export async function probeCredentials(input) {
18
+ const { baseUrl, region, fetchImpl = fetch, timeoutMs = 10_000 } = input;
19
+ const url = new URL('/api/log-groups', baseUrl);
20
+ if (region !== null && region !== '')
21
+ url.searchParams.set('region', region);
22
+ url.searchParams.set('limit', '1');
23
+ try {
24
+ const response = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
25
+ if (response.ok)
26
+ return { ok: true };
27
+ const body = (await response.json().catch(() => null));
28
+ const message = body?.error ?? `HTTP ${response.status}`;
29
+ return { ok: false, code: body?.code, message, credentialProblem: true };
30
+ }
31
+ catch (error) {
32
+ // A transport failure is not a credentials problem.
33
+ return {
34
+ ok: false,
35
+ message: error instanceof Error ? error.message : String(error),
36
+ credentialProblem: false,
37
+ };
38
+ }
39
+ }
40
+ /** Runs a login command with the terminal attached, so the browser flow works. */
41
+ export function runLogin(command, options = {}) {
42
+ const { spawnImpl = spawn, stdio = 'inherit' } = options;
43
+ return new Promise((resolveExit) => {
44
+ let child;
45
+ try {
46
+ child = spawnImpl('aws', command, { stdio, shell: false });
47
+ }
48
+ catch {
49
+ resolveExit(127);
50
+ return;
51
+ }
52
+ child.on('error', () => resolveExit(127));
53
+ child.on('close', (code) => resolveExit(code ?? 1));
54
+ });
55
+ }
56
+ /**
57
+ * True when there is no browser to drive the login (SSH, headless).
58
+ *
59
+ * `WATCH_TAIL_HEADLESS` overrides the guess in both directions: `1` forces the
60
+ * `--remote` flow, `0` forces the browser flow on a host that looks headless
61
+ * (a Linux container with a browser proxy, for example).
62
+ */
63
+ export function isHeadless(env = process.env) {
64
+ if (env.WATCH_TAIL_HEADLESS === '1')
65
+ return true;
66
+ if (env.WATCH_TAIL_HEADLESS === '0')
67
+ return false;
68
+ if (env.SSH_CONNECTION !== undefined && env.SSH_CONNECTION !== '')
69
+ return true;
70
+ if (process.platform === 'linux' && env.DISPLAY === undefined)
71
+ return true;
72
+ return false;
73
+ }
package/dist/cli/ui.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * banner, spinner, autocomplete prompts. Anything else (pipes, CI, `--no-color`)
6
6
  * falls back to plain lines, which also keeps the integration tests readable.
7
7
  */
8
- import { autocomplete, cancel, intro, isCancel, log, outro, spinner } from '@clack/prompts';
8
+ import { autocomplete, cancel, confirm, intro, isCancel, log, outro, spinner, } from '@clack/prompts';
9
9
  /** Decides whether the process can render animated output and ask questions. */
10
10
  export function isInteractive(env = process.env) {
11
11
  if (env.CI !== undefined && env.CI !== '' && env.CI !== 'false')
@@ -39,6 +39,7 @@ export function createUi(options = {}) {
39
39
  const fallback = initial ?? choices[0]?.value ?? null;
40
40
  return fallback;
41
41
  },
42
+ confirm: async () => false,
42
43
  };
43
44
  }
44
45
  return {
@@ -71,5 +72,11 @@ export function createUi(options = {}) {
71
72
  }
72
73
  return String(answer);
73
74
  },
75
+ confirm: async (message, initial = true) => {
76
+ const answer = await confirm({ message, initialValue: initial });
77
+ if (isCancel(answer))
78
+ return false;
79
+ return answer === true;
80
+ },
74
81
  };
75
82
  }
@@ -185,7 +185,7 @@ const EMULATOR_CREDENTIALS = { accessKeyId: 'test', secretAccessKey: 'test' };
185
185
  * `AWS_DEFAULT_REGION`, which is how `--region` (or a region resolved by
186
186
  * {@link resolveRunRegion}) wins over the profile.
187
187
  */
188
- export function buildChildEnv({ base, profile = null, region = null, endpoint = null, }) {
188
+ export function buildChildEnv({ base, profile = null, region = null, endpoint = null, clearStaticKeys = false, }) {
189
189
  const env = { ...base };
190
190
  const name = normalize(profile);
191
191
  const emulator = normalize(endpoint);
@@ -194,6 +194,10 @@ export function buildChildEnv({ base, profile = null, region = null, endpoint =
194
194
  for (const key of [...LOCAL_OVERRIDE_KEYS, ...REGION_KEYS])
195
195
  env[key] = '';
196
196
  }
197
+ if (name === null && clearStaticKeys) {
198
+ env.AWS_ACCESS_KEY_ID = '';
199
+ env.AWS_SECRET_ACCESS_KEY = '';
200
+ }
197
201
  if (emulator !== null) {
198
202
  env.AWS_ENDPOINT_URL = emulator;
199
203
  env.AWS_ENDPOINT_URL_LOGS = emulator;
@@ -211,6 +215,54 @@ export function buildChildEnv({ base, profile = null, region = null, endpoint =
211
215
  }
212
216
  return env;
213
217
  }
218
+ /**
219
+ * Parses a dotenv file into a plain map.
220
+ *
221
+ * Only `KEY=value` lines are read, with optional single or double quotes; the
222
+ * values are never applied to the caller's environment.
223
+ */
224
+ export function parseEnvFile(text) {
225
+ const values = {};
226
+ for (const raw of text.split('\n')) {
227
+ const line = raw.trim();
228
+ if (line.length === 0 || line.startsWith('#'))
229
+ continue;
230
+ const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
231
+ if (match === null)
232
+ continue;
233
+ const value = match[2].trim().replace(/^(['"])(.*)\1$/, '$2');
234
+ values[match[1]] = value;
235
+ }
236
+ return values;
237
+ }
238
+ /** Reads `<appRoot>/.env.local`, or an empty map when it is missing. */
239
+ export function readLocalEnvValues(appRoot) {
240
+ return parseEnvFile(readTextFile(join(appRoot, LOCAL_ENV_FILE)));
241
+ }
242
+ /**
243
+ * Neutralises a `.env.local` that sits next to the app.
244
+ *
245
+ * The app applies that file to its own process (so `pnpm dev` can point at a
246
+ * local emulator), but the CLI is documented as real-AWS-by-default. A value
247
+ * from the file is therefore suppressed unless the caller already set the same
248
+ * key in their environment, which is theirs to decide. Blanking is how "unset"
249
+ * is expressed: dotenv loading never overrides a variable that exists, and the
250
+ * SDK ignores empty values.
251
+ */
252
+ export function suppressLocalEnvValues(input) {
253
+ const { env, values, keys = LOCAL_OVERRIDE_KEYS } = input;
254
+ const next = { ...env };
255
+ const suppressed = [];
256
+ for (const key of keys) {
257
+ if (values[key] === undefined)
258
+ continue;
259
+ if (normalize(env[key]) !== null)
260
+ continue; // the caller's own value wins
261
+ next[key] = '';
262
+ suppressed.push(key);
263
+ }
264
+ return { env: next, suppressed };
265
+ }
214
266
  /** True when the endpoint points at a local emulator rather than real AWS. */
215
267
  export function isEmulatorEndpoint(endpoint) {
216
268
  let hostname = '';
@@ -243,6 +295,10 @@ export function describeChildEnv(env) {
243
295
  export function readConfigText(env = process.env) {
244
296
  return readTextFile(profileFiles(env).configFile);
245
297
  }
298
+ /** Reads the shared credentials file, or an empty string when it is missing. */
299
+ export function readCredentialsText(env = process.env) {
300
+ return readTextFile(profileFiles(env).credentialsFile);
301
+ }
246
302
  /** True when `.env.local` (the floci settings) exists in the repository root. */
247
303
  export function isLocalEnvPresent(cwd = process.cwd()) {
248
304
  return existsSync(join(cwd, LOCAL_ENV_FILE));
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Credential diagnosis for the CLI: why AWS is refusing, and which login command
3
+ * fixes it.
4
+ *
5
+ * The app itself reports the failure (`/api/log-groups` answers 502 with a code
6
+ * such as `missing-credentials`), so this module only has to answer the second
7
+ * half of the question: what should the user run? That depends on how the
8
+ * profile is configured, which is read from the user's own AWS files.
9
+ */
10
+ /** `true` when the failure is about credentials rather than about the region or the API. */
11
+ export function isCredentialFailure(code, message) {
12
+ if (code === 'missing-credentials' || code === 'access-denied')
13
+ return true;
14
+ return /credential|expired|sso|sign in|sign-in|reauthenticate|not authorized|unrecognized client/i.test(message);
15
+ }
16
+ /**
17
+ * Classifies the message the SDK produced.
18
+ *
19
+ * The three shapes that matter come from the AWS CLI's own providers: an SSO
20
+ * session that needs `aws sso login`, a console sign-in session that needs
21
+ * `aws login`, and static keys that are simply wrong.
22
+ */
23
+ export function classifyCredentialFailure(message, code) {
24
+ if (/sso login|sso session|sso token/i.test(message))
25
+ return 'sso-expired';
26
+ if (/reauthenticate|sign in|sign-in|login session/i.test(message))
27
+ return 'missing';
28
+ if (/could not load credentials|unable to locate credentials|no credentials/i.test(message)) {
29
+ return 'missing';
30
+ }
31
+ if (code === 'access-denied' ||
32
+ /invalidclienttoken|unrecognizedclient|invalid signature|forbidden/i.test(message)) {
33
+ return 'unusable';
34
+ }
35
+ return code === 'missing-credentials' ? 'missing' : 'other';
36
+ }
37
+ /** Splits an ini-style file into `section -> lines`. */
38
+ function sections(text) {
39
+ const map = new Map();
40
+ let current = null;
41
+ for (const raw of text.split('\n')) {
42
+ const line = raw.trim();
43
+ if (line.length === 0 || line.startsWith('#') || line.startsWith(';'))
44
+ continue;
45
+ const header = /^\[([^\]]+)\]$/.exec(line);
46
+ if (header !== null) {
47
+ current = header[1].trim();
48
+ if (!map.has(current))
49
+ map.set(current, []);
50
+ continue;
51
+ }
52
+ if (current !== null)
53
+ map.get(current)?.push(line);
54
+ }
55
+ return map;
56
+ }
57
+ /** Normalises a profile's section name: `[profile x]` in config, `[x]` in credentials. */
58
+ function sectionNames(profile) {
59
+ return profile === 'default' ? ['default', 'profile default'] : [profile, `profile ${profile}`];
60
+ }
61
+ /** The profile's lines from whichever file defines it first. */
62
+ function profileLines(configText, credentialsText, profile) {
63
+ for (const text of [configText, credentialsText]) {
64
+ const map = sections(text);
65
+ for (const name of sectionNames(profile)) {
66
+ const lines = map.get(name);
67
+ if (lines !== undefined)
68
+ return lines;
69
+ }
70
+ }
71
+ return [];
72
+ }
73
+ /** Reads a `key = value` setting from a profile's lines. */
74
+ function setting(lines, key) {
75
+ const pattern = new RegExp(`^${key}\\s*=\\s*(.*)$`, 'i');
76
+ for (const line of lines) {
77
+ const match = pattern.exec(line);
78
+ if (match !== null) {
79
+ const value = match[1].trim();
80
+ if (value.length > 0)
81
+ return value;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ /**
87
+ * Works out how a profile authenticates.
88
+ *
89
+ * Order matters: `sso_session` and `login_session` are the modern providers, and
90
+ * a profile that has both (it should not) is treated as SSO because that is the
91
+ * one `aws sso login` repairs.
92
+ */
93
+ export function readProfileStyle(configText, credentialsText, profile) {
94
+ const lines = profileLines(configText, credentialsText, profile);
95
+ if (lines.length === 0) {
96
+ const configured = [...sections(configText).keys()].some((name) => name.startsWith('profile '));
97
+ return configured ? 'unconfigured' : 'unconfigured';
98
+ }
99
+ if (setting(lines, 'sso_session') !== null || setting(lines, 'sso_start_url') !== null)
100
+ return 'sso';
101
+ if (setting(lines, 'login_session') !== null)
102
+ return 'login';
103
+ if (setting(lines, 'credential_process') !== null)
104
+ return 'process';
105
+ if (setting(lines, 'aws_access_key_id') !== null)
106
+ return 'static';
107
+ return 'unconfigured';
108
+ }
109
+ /**
110
+ * Picks the login command for a failure.
111
+ *
112
+ * Returns `null` when no login command applies: static keys are fixed with
113
+ * `aws configure`, and a `credential_process` has to be debugged in place.
114
+ * `aws login` also refuses to convert a profile that already uses another style,
115
+ * which is why the style is checked before suggesting it.
116
+ */
117
+ export function loginAdvice(input) {
118
+ const { style, failure, profile, remote = false } = input;
119
+ const named = profile !== null && profile !== 'default';
120
+ const remoteFlag = remote ? ['--remote'] : [];
121
+ // The profile's own style decides the command. A failure message that
122
+ // mentions SSO while the profile is not SSO-configured must not send anyone
123
+ // to `aws sso login`: AWS rejects that with "Missing the following required
124
+ // SSO configuration values" (verified against the real CLI).
125
+ if (style === 'sso') {
126
+ return {
127
+ command: ['sso', 'login', ...(named ? ['--profile', profile] : []), ...remoteFlag],
128
+ hint: named
129
+ ? `Sign in to the SSO session for ${profile}`
130
+ : 'Sign in to the default SSO session',
131
+ };
132
+ }
133
+ if (style === 'login' || style === 'unconfigured') {
134
+ // `aws login` also creates the session, so it is the right answer for a
135
+ // profile that has no credentials configured yet.
136
+ const mismatch = failure === 'sso-expired';
137
+ return {
138
+ command: ['login', ...(named ? ['--profile', profile] : []), ...remoteFlag],
139
+ hint: named
140
+ ? `Sign in to the AWS console and save the session in ${profile}`
141
+ : 'Sign in to the AWS console and save the session in the default profile',
142
+ ...(mismatch ? { note: 'the SSO session that failed belongs to a different profile' } : {}),
143
+ };
144
+ }
145
+ // Static keys are fixed with `aws configure`; a credential_process has to be
146
+ // debugged where it is defined.
147
+ return null;
148
+ }
149
+ /** One-line description of a style, for messages. */
150
+ export function describeStyle(style) {
151
+ switch (style) {
152
+ case 'sso':
153
+ return 'SSO';
154
+ case 'login':
155
+ return 'console sign-in';
156
+ case 'static':
157
+ return 'static access keys';
158
+ case 'process':
159
+ return 'a credential_process';
160
+ default:
161
+ return 'no credentials';
162
+ }
163
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared wire types between the SvelteKit server routes and the browser UI.
3
+ * Keep this file free of runtime imports so both sides can import it.
4
+ */
5
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watch-tail",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Tail Amazon CloudWatch Logs in your browser: a local SvelteKit UI with live and historic streaming, ambient AWS credentials and floci support",
5
5
  "keywords": [
6
6
  "aws",