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.
- package/README.md +30 -0
- package/build/client/_app/immutable/assets/0.CocWI5K4.css +2 -0
- package/build/client/_app/immutable/assets/0.CocWI5K4.css.br +0 -0
- package/build/client/_app/immutable/assets/0.CocWI5K4.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{BiIbMEwd.js → 05WsiB4A.js} +1 -1
- package/build/client/_app/immutable/chunks/05WsiB4A.js.br +0 -0
- package/build/client/_app/immutable/chunks/05WsiB4A.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DwpzNa8H.js → DyIYFD7i.js} +1 -1
- package/build/client/_app/immutable/chunks/DyIYFD7i.js.br +0 -0
- package/build/client/_app/immutable/chunks/DyIYFD7i.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.B-LBdafb.js → app.D0_YFW4X.js} +2 -2
- package/build/client/_app/immutable/entry/app.D0_YFW4X.js.br +0 -0
- package/build/client/_app/immutable/entry/app.D0_YFW4X.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.D1rrHrg2.js +1 -0
- package/build/client/_app/immutable/entry/start.D1rrHrg2.js.br +0 -0
- package/build/client/_app/immutable/entry/start.D1rrHrg2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.dLH1x-yV.js → 1.D1vhnOgO.js} +1 -1
- package/build/client/_app/immutable/nodes/1.D1vhnOgO.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.D1vhnOgO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.PY6hzMBU.js → 2.BPdL6B7F.js} +1 -1
- package/build/client/_app/immutable/nodes/2.BPdL6B7F.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BPdL6B7F.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +4 -4
- package/build/index.js +4 -4
- package/build/server/chunks/chunks/{internal.js-Bp_eP8ac.js → internal.js-B-DZpK-L.js} +2 -2
- package/build/server/chunks/chunks/{internal.js-Bp_eP8ac.js.map → internal.js-B-DZpK-L.js.map} +1 -1
- package/build/server/chunks/chunks/{state.js-dp_P2xFv.js → state.js-V2NYGhy_.js} +2 -2
- package/build/server/chunks/chunks/{state.js-dp_P2xFv.js.map → state.js-V2NYGhy_.js.map} +1 -1
- package/build/server/chunks/entries/fallbacks/{error.svelte.js-Cr_jQ6By.js → error.svelte.js-CAasf3PF.js} +3 -3
- package/build/server/chunks/entries/fallbacks/{error.svelte.js-Cr_jQ6By.js.map → error.svelte.js-CAasf3PF.js.map} +1 -1
- package/build/server/chunks/entries/pages/{_page.svelte.js-BYE11gdZ.js → _page.svelte.js-DzAAqWz2.js} +3 -3
- package/build/server/chunks/entries/pages/{_page.svelte.js-BYE11gdZ.js.map → _page.svelte.js-DzAAqWz2.js.map} +1 -1
- package/build/server/chunks/{handler-BTS3V5-R.js → handler-CuWqZDuC.js} +3 -3
- package/build/server/chunks/{handler-BTS3V5-R.js.map → handler-CuWqZDuC.js.map} +1 -1
- package/build/server/chunks/{index.js-CqJBHkzQ.js → index.js-NHTL74z2.js} +2 -2
- package/build/server/chunks/{index.js-CqJBHkzQ.js.map → index.js-NHTL74z2.js.map} +1 -1
- package/build/server/chunks/{manifest.js-DjD36Niy.js → manifest.js-8g4gjxwf.js} +5 -5
- package/build/server/chunks/{manifest.js-DjD36Niy.js.map → manifest.js-8g4gjxwf.js.map} +1 -1
- package/build/server/chunks/nodes/{0.js-Dl0J9d3p.js → 0.js-Bg4_hDjk.js} +3 -3
- package/build/server/chunks/nodes/{0.js-Dl0J9d3p.js.map → 0.js-Bg4_hDjk.js.map} +1 -1
- package/build/server/chunks/nodes/{1.js-rafEmQ-L.js → 1.js-CDSk3TvW.js} +3 -3
- package/build/server/chunks/nodes/{1.js-rafEmQ-L.js.map → 1.js-CDSk3TvW.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-BVhBcWrN.js +9 -0
- package/build/server/chunks/nodes/{2.js-Dc-ZnMEK.js.map → 2.js-BVhBcWrN.js.map} +1 -1
- package/dist/cli/index.js +91 -3
- package/dist/cli/preflight.js +73 -0
- package/dist/cli/ui.js +8 -1
- package/dist/lib/cli/aws.js +57 -1
- package/dist/lib/cli/credentials.js +163 -0
- package/dist/lib/types.js +5 -0
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/0.B1NsRRrX.css +0 -2
- package/build/client/_app/immutable/assets/0.B1NsRRrX.css.br +0 -0
- package/build/client/_app/immutable/assets/0.B1NsRRrX.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BiIbMEwd.js.br +0 -0
- package/build/client/_app/immutable/chunks/BiIbMEwd.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DwpzNa8H.js.br +0 -0
- package/build/client/_app/immutable/chunks/DwpzNa8H.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.B-LBdafb.js.br +0 -0
- package/build/client/_app/immutable/entry/app.B-LBdafb.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DO6ike1m.js +0 -1
- package/build/client/_app/immutable/entry/start.DO6ike1m.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DO6ike1m.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.dLH1x-yV.js.br +0 -1
- package/build/client/_app/immutable/nodes/1.dLH1x-yV.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.PY6hzMBU.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.PY6hzMBU.js.gz +0 -0
- package/build/server/chunks/nodes/2.js-Dc-ZnMEK.js +0 -9
- /package/build/client/_app/immutable/nodes/{0.D5bYt7iX.js → 0.DHBUWbOo.js} +0 -0
- /package/build/client/_app/immutable/nodes/{0.D5bYt7iX.js.br → 0.DHBUWbOo.js.br} +0 -0
- /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.
|
|
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-
|
|
16
|
-
__memo(() => import('./nodes/1.js-
|
|
17
|
-
__memo(() => import('./nodes/2.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-
|
|
70
|
+
//# sourceMappingURL=manifest.js-8g4gjxwf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js-
|
|
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.
|
|
5
|
-
const stylesheets = ["_app/immutable/assets/0.
|
|
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-
|
|
9
|
+
//# sourceMappingURL=0.js-Bg4_hDjk.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"0.js-
|
|
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-
|
|
4
|
-
const imports = ["_app/immutable/nodes/1.
|
|
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-
|
|
9
|
+
//# sourceMappingURL=1.js-CDSk3TvW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1.js-
|
|
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-
|
|
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:
|
|
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 (
|
|
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
|
}
|
package/dist/lib/cli/aws.js
CHANGED
|
@@ -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
|
+
}
|
package/package.json
CHANGED