@@ -6,7 +6,14 @@ import {stringify as stringifyYaml} from 'yaml'
66
77import { formatArticleNotFoundMessage } from '../../lib/article-not-found.js'
88import { DevcenterClient } from '../../lib/devcenter-client.js'
9- import { articleApiPath , mdFilePath , slugFromArticleUrl } from '../../lib/paths.js'
9+ import { getHerokuApiToken } from '../../lib/heroku-api-auth.js'
10+ import {
11+ articleApiPath ,
12+ getDevcenterBaseUrl ,
13+ mdFilePath ,
14+ privateArticleShowPath ,
15+ slugFromArticleUrl ,
16+ } from '../../lib/paths.js'
1017
1118type ArticleJson = {
1219 content : string
@@ -24,6 +31,10 @@ export default class Pull extends Command {
2431 }
2532 static description = 'save a local copy of a Dev Center article'
2633 static flags = {
34+ debug : Flags . boolean ( {
35+ description :
36+ 'log HTTP status and response shape for public, authenticated public, and private API article fetch' ,
37+ } ) ,
2738 force : Flags . boolean ( {
2839 char : 'f' ,
2940 description : 'overwrite an existing local file without prompting' ,
@@ -41,9 +52,55 @@ export default class Pull extends Command {
4152 )
4253 }
4354
55+ const dbg = ( message : string ) => {
56+ if ( flags . debug ) {
57+ process . stdout . write ( `devcenter: ${ message } \n` )
58+ }
59+ }
60+
4461 const client = new DevcenterClient ( )
45- const { body, ok} = await client . getJson < ArticleJson > ( articleApiPath ( slug ) )
46- const articleOk = ok && body ?. slug === slug
62+ const path = articleApiPath ( slug )
63+ dbg ( `baseUrl=${ getDevcenterBaseUrl ( ) } path=${ path } expectedSlug=${ slug } ` )
64+
65+ let { body, ok, status} = await client . getJson < ArticleJson > ( path )
66+ let articleOk = ok && body ?. slug === slug
67+ logArticleJsonFetch ( dbg , {
68+ expectedSlug : slug , label : 'no auth' , path, res : { body, ok, status} ,
69+ } )
70+
71+ let token : string | undefined
72+ try {
73+ token = getHerokuApiToken ( )
74+ } catch ( error ) {
75+ const msg = error instanceof Error ? error . message : String ( error )
76+ dbg ( `Heroku ~/.netrc token unavailable: ${ msg } ` )
77+ }
78+
79+ if ( ! articleOk && token ) {
80+ dbg ( 'retrying GET with Heroku ~/.netrc token (public JSON)' )
81+ const authed = await client . getJson < ArticleJson > ( path , undefined , { token} )
82+ body = authed . body
83+ ok = authed . ok
84+ status = authed . status
85+ articleOk = ok && body ?. slug === slug
86+ logArticleJsonFetch ( dbg , {
87+ expectedSlug : slug , label : 'authenticated public' , path, res : authed ,
88+ } )
89+ }
90+
91+ if ( ! articleOk && token ) {
92+ const privatePath = privateArticleShowPath ( slug )
93+ dbg ( `retrying GET private API ${ privatePath } ` )
94+ const priv = await client . getJson < ArticleJson > ( privatePath , undefined , { token} )
95+ body = priv . body
96+ ok = priv . ok
97+ status = priv . status
98+ articleOk = ok && body ?. slug === slug
99+ logArticleJsonFetch ( dbg , {
100+ expectedSlug : slug , label : 'private API' , path : privatePath , res : priv ,
101+ } )
102+ }
103+
47104 if ( ! articleOk ) {
48105 const msg = await formatArticleNotFoundMessage ( client , slug )
49106 this . error ( msg , { exit : 1 } )
@@ -69,3 +126,32 @@ export default class Pull extends Command {
69126 this . log ( `"${ metadata . title } " article saved as ${ filePath } ` )
70127 }
71128}
129+
130+ function logArticleJsonFetch (
131+ dbg : ( m : string ) => void ,
132+ opts : {
133+ expectedSlug : string
134+ label : string
135+ path : string
136+ res : { body : ArticleJson ; ok : boolean ; status : number }
137+ } ,
138+ ) : void {
139+ const { expectedSlug, label, path, res} = opts
140+ const { body, ok, status} = res
141+ const slugPart = body ?. slug === undefined ? '(missing)' : JSON . stringify ( body . slug )
142+ const titlePart
143+ = body ?. title === undefined ? '(missing)' : JSON . stringify ( String ( body . title ) . slice ( 0 , 80 ) )
144+ dbg ( `GET ${ path } (${ label } ): status=${ status } ok=${ ok } body.slug=${ slugPart } title=${ titlePart } ` )
145+ dbg ( `slugMatch=${ String ( body ?. slug === expectedSlug ) } (want ${ JSON . stringify ( expectedSlug ) } )` )
146+ if ( body && typeof body === 'object' ) {
147+ dbg ( `response keys: ${ Object . keys ( body as object ) . sort ( ) . join ( ', ' ) } ` )
148+ dbg ( 'response body (full JSON):' )
149+ try {
150+ for ( const line of JSON . stringify ( body , undefined , 2 ) . split ( '\n' ) ) {
151+ dbg ( ` ${ line } ` )
152+ }
153+ } catch {
154+ dbg ( ` (could not stringify body: ${ String ( body ) } )` )
155+ }
156+ }
157+ }
0 commit comments