@heroku-cli/plugin-devcenter 0.0.2 → 2.0.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/LICENSE.txt +22 -0
- package/README.md +132 -17
- package/dist/commands/devcenter/open.d.ts +9 -0
- package/dist/commands/devcenter/open.d.ts.map +1 -0
- package/dist/commands/devcenter/open.js +33 -0
- package/dist/commands/devcenter/open.js.map +1 -0
- package/dist/commands/devcenter/preview.d.ts +14 -0
- package/dist/commands/devcenter/preview.d.ts.map +1 -0
- package/dist/commands/devcenter/preview.js +40 -0
- package/dist/commands/devcenter/preview.js.map +1 -0
- package/dist/commands/devcenter/pull.d.ts +12 -0
- package/dist/commands/devcenter/pull.d.ts.map +1 -0
- package/dist/commands/devcenter/pull.js +52 -0
- package/dist/commands/devcenter/pull.js.map +1 -0
- package/dist/commands/devcenter/push.d.ts +9 -0
- package/dist/commands/devcenter/push.d.ts.map +1 -0
- package/dist/commands/devcenter/push.js +105 -0
- package/dist/commands/devcenter/push.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/article-file.d.ts +21 -0
- package/dist/lib/article-file.d.ts.map +1 -0
- package/dist/lib/article-file.js +48 -0
- package/dist/lib/article-file.js.map +1 -0
- package/dist/lib/article-not-found.d.ts +3 -0
- package/dist/lib/article-not-found.d.ts.map +1 -0
- package/dist/lib/article-not-found.js +16 -0
- package/dist/lib/article-not-found.js.map +1 -0
- package/dist/lib/article-resolve.d.ts +23 -0
- package/dist/lib/article-resolve.d.ts.map +1 -0
- package/dist/lib/article-resolve.js +61 -0
- package/dist/lib/article-resolve.js.map +1 -0
- package/dist/lib/article-split.d.ts +9 -0
- package/dist/lib/article-split.d.ts.map +1 -0
- package/dist/lib/article-split.js +15 -0
- package/dist/lib/article-split.js.map +1 -0
- package/dist/lib/devcenter-client.d.ts +39 -0
- package/dist/lib/devcenter-client.d.ts.map +1 -0
- package/dist/lib/devcenter-client.js +119 -0
- package/dist/lib/devcenter-client.js.map +1 -0
- package/dist/lib/heroku-api-auth.d.ts +7 -0
- package/dist/lib/heroku-api-auth.d.ts.map +1 -0
- package/dist/lib/heroku-api-auth.js +12 -0
- package/dist/lib/heroku-api-auth.js.map +1 -0
- package/dist/lib/paths.d.ts +18 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +48 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preview-server.d.ts +17 -0
- package/dist/lib/preview-server.d.ts.map +1 -0
- package/dist/lib/preview-server.js +94 -0
- package/dist/lib/preview-server.js.map +1 -0
- package/dist/lib/preview-templates.d.ts +7 -0
- package/dist/lib/preview-templates.d.ts.map +1 -0
- package/dist/lib/preview-templates.js +93 -0
- package/dist/lib/preview-templates.js.map +1 -0
- package/oclif.manifest.json +172 -1
- package/package.json +78 -49
- package/lib/commands/docs.d.ts +0 -25
- package/lib/commands/docs.js +0 -29
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DevcenterClient } from './devcenter-client.js';
|
|
2
|
+
export type ArticleJson = {
|
|
3
|
+
content: string;
|
|
4
|
+
id: number | string;
|
|
5
|
+
slug: string;
|
|
6
|
+
title: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Resolves article JSON using the same sequence as legacy Dev Center tooling: public JSON,
|
|
10
|
+
* then authenticated public JSON, then private API — matching `devcenter:pull`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fetchArticleJsonForSlug(client: DevcenterClient, slug: string, token: string | undefined, dbg?: (msg: string) => void): Promise<ArticleJson | null>;
|
|
13
|
+
export declare function logArticleJsonFetch(dbg: ((msg: string) => void) | undefined, opts: {
|
|
14
|
+
expectedSlug: string;
|
|
15
|
+
label: string;
|
|
16
|
+
path: string;
|
|
17
|
+
res: {
|
|
18
|
+
body: ArticleJson;
|
|
19
|
+
ok: boolean;
|
|
20
|
+
status: number;
|
|
21
|
+
};
|
|
22
|
+
}): void;
|
|
23
|
+
//# sourceMappingURL=article-resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article-resolve.d.ts","sourceRoot":"","sources":["../../src/lib/article-resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAA;AAI1D,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA2C7B;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,EACxC,IAAI,EAAE;IACJ,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE;QAAC,IAAI,EAAE,WAAW,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,CAAA;CACtD,GACA,IAAI,CAgBN"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { articleApiPath, privateArticleShowPath } from './paths.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves article JSON using the same sequence as legacy Dev Center tooling: public JSON,
|
|
4
|
+
* then authenticated public JSON, then private API — matching `devcenter:pull`.
|
|
5
|
+
*/
|
|
6
|
+
export async function fetchArticleJsonForSlug(client, slug, token, dbg) {
|
|
7
|
+
const path = articleApiPath(slug);
|
|
8
|
+
let { body, ok, status } = await client.getJson(path);
|
|
9
|
+
let articleOk = ok && body?.slug === slug;
|
|
10
|
+
logArticleJsonFetch(dbg, {
|
|
11
|
+
expectedSlug: slug, label: 'no auth', path, res: { body, ok, status },
|
|
12
|
+
});
|
|
13
|
+
if (!token) {
|
|
14
|
+
dbg?.('Heroku credentials not available; skipping authenticated Dev Center requests');
|
|
15
|
+
}
|
|
16
|
+
if (!articleOk && token) {
|
|
17
|
+
dbg?.('retrying GET with Heroku CLI credentials (public JSON)');
|
|
18
|
+
const authed = await client.getJson(path, undefined, { token });
|
|
19
|
+
body = authed.body;
|
|
20
|
+
ok = authed.ok;
|
|
21
|
+
status = authed.status;
|
|
22
|
+
articleOk = ok && body?.slug === slug;
|
|
23
|
+
logArticleJsonFetch(dbg, {
|
|
24
|
+
expectedSlug: slug, label: 'authenticated public', path, res: authed,
|
|
25
|
+
});
|
|
26
|
+
if (!articleOk) {
|
|
27
|
+
const privatePath = privateArticleShowPath(slug);
|
|
28
|
+
dbg?.(`retrying GET private API ${privatePath}`);
|
|
29
|
+
const priv = await client.getJson(privatePath, undefined, { token });
|
|
30
|
+
body = priv.body;
|
|
31
|
+
ok = priv.ok;
|
|
32
|
+
status = priv.status;
|
|
33
|
+
articleOk = ok && body?.slug === slug;
|
|
34
|
+
logArticleJsonFetch(dbg, {
|
|
35
|
+
expectedSlug: slug, label: 'private API', path: privatePath, res: priv,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!articleOk) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return body;
|
|
43
|
+
}
|
|
44
|
+
export function logArticleJsonFetch(dbg, opts) {
|
|
45
|
+
if (!dbg)
|
|
46
|
+
return;
|
|
47
|
+
const { expectedSlug, label, path, res } = opts;
|
|
48
|
+
const { body, ok, status } = res;
|
|
49
|
+
const slugPart = body?.slug === undefined ? '(missing)' : JSON.stringify(body.slug);
|
|
50
|
+
const titlePart = body?.title === undefined ? '(missing)' : JSON.stringify(String(body.title).slice(0, 80));
|
|
51
|
+
dbg(`GET ${path} (${label}): status=${status} ok=${ok} body.slug=${slugPart} title=${titlePart}`);
|
|
52
|
+
dbg(`slugMatch=${String(body?.slug === expectedSlug)} (want ${JSON.stringify(expectedSlug)})`);
|
|
53
|
+
if (body && typeof body === 'object') {
|
|
54
|
+
dbg(`response keys: ${Object.keys(body).sort().join(', ')}`);
|
|
55
|
+
if ('content' in body) {
|
|
56
|
+
const c = body.content;
|
|
57
|
+
dbg(`content: ${typeof c === 'string' ? `${c.length} chars` : String(c)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=article-resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article-resolve.js","sourceRoot":"","sources":["../../src/lib/article-resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split Dev Center article file into YAML front matter and markdown body
|
|
3
|
+
* (same rule as the Ruby gem: first blank line separates YAML from content).
|
|
4
|
+
*/
|
|
5
|
+
export declare function splitMetadataBody(src: string): {
|
|
6
|
+
body: string;
|
|
7
|
+
yamlText: string;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=article-split.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article-split.d.ts","sourceRoot":"","sources":["../../src/lib/article-split.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;CAAC,CAUhF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split Dev Center article file into YAML front matter and markdown body
|
|
3
|
+
* (same rule as the Ruby gem: first blank line separates YAML from content).
|
|
4
|
+
*/
|
|
5
|
+
export function splitMetadataBody(src) {
|
|
6
|
+
const re = /\r*\n\r*\n/;
|
|
7
|
+
const match = re.exec(src);
|
|
8
|
+
if (!match || match.index === undefined) {
|
|
9
|
+
throw new Error('Invalid article file: expected YAML front matter, a blank line, then markdown content.');
|
|
10
|
+
}
|
|
11
|
+
const yamlText = src.slice(0, match.index).trimEnd();
|
|
12
|
+
const body = src.slice(match.index + match[0].length);
|
|
13
|
+
return { body, yamlText };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=article-split.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article-split.js","sourceRoot":"","sources":["../../src/lib/article-split.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,EAAE,GAAG,YAAY,CAAA;IACvB,MAAM,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAA;IAC3G,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAA;IACpD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;IACrD,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAC,CAAA;AACzB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare class DevcenterClient {
|
|
2
|
+
private readonly baseUrl;
|
|
3
|
+
constructor(baseUrl?: string);
|
|
4
|
+
authForm(method: 'POST' | 'PUT', path: string, token: string, params: Record<string, string>): Promise<{
|
|
5
|
+
body: unknown;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
status: number;
|
|
8
|
+
}>;
|
|
9
|
+
checkBrokenLinks(token: string, content: string): Promise<{
|
|
10
|
+
body: unknown;
|
|
11
|
+
ok: boolean;
|
|
12
|
+
status: number;
|
|
13
|
+
}>;
|
|
14
|
+
getJson<T>(path: string, query?: Record<string, string>, options?: {
|
|
15
|
+
token?: string;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
body: T;
|
|
18
|
+
ok: boolean;
|
|
19
|
+
status: number;
|
|
20
|
+
}>;
|
|
21
|
+
head(path: string): Promise<{
|
|
22
|
+
location?: string;
|
|
23
|
+
notFound: boolean;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
redirect: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
updateArticle(token: string, articleId: number | string, params: Record<string, string>): Promise<{
|
|
28
|
+
body: unknown;
|
|
29
|
+
ok: boolean;
|
|
30
|
+
status: number;
|
|
31
|
+
}>;
|
|
32
|
+
validateArticle(token: string, articleId: number | string, params: Record<string, string>): Promise<{
|
|
33
|
+
body: unknown;
|
|
34
|
+
ok: boolean;
|
|
35
|
+
status: number;
|
|
36
|
+
}>;
|
|
37
|
+
private url;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=devcenter-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devcenter-client.d.ts","sourceRoot":"","sources":["../../src/lib/devcenter-client.ts"],"names":[],"mappings":"AA2DA,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,MAA8B;IAE9D,QAAQ,CACZ,MAAM,EAAE,MAAM,GAAG,KAAK,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;KAAC,CAAC;IAuBnD,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;cAvBnC,OAAO;YAAM,OAAO;gBAAU,MAAM;;IA2BhD,OAAO,CAAC,CAAC,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,GACzB,OAAO,CAAC;QAAC,IAAI,EAAE,CAAC,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;KAAC,CAAC;IA8B7C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;KAAC,CAAC;IAapG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;cA1E3E,OAAO;YAAM,OAAO;gBAAU,MAAM;;IA8EhD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;cA9E7E,OAAO;YAAM,OAAO;gBAAU,MAAM;;IAkFtD,OAAO,CAAC,GAAG;CAKZ"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import * as https from 'node:https';
|
|
3
|
+
import { URL } from 'node:url';
|
|
4
|
+
import { basicAuthHeaders } from './heroku-api-auth.js';
|
|
5
|
+
import { brokenLinkChecksPath, getDevcenterBaseUrl, updateArticlePath, validateArticlePath, } from './paths.js';
|
|
6
|
+
const DEFAULT_HEADERS = { 'User-agent': 'DevCenterCLI' };
|
|
7
|
+
function nodeRequest(method, urlString, options = {}) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const u = new URL(urlString);
|
|
10
|
+
const lib = u.protocol === 'https:' ? https : http;
|
|
11
|
+
const req = lib.request({
|
|
12
|
+
headers: options.headers,
|
|
13
|
+
hostname: u.hostname,
|
|
14
|
+
method,
|
|
15
|
+
path: `${u.pathname}${u.search}`,
|
|
16
|
+
port: u.port || (u.protocol === 'https:' ? 443 : 80),
|
|
17
|
+
}, res => {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
res.on('data', c => {
|
|
20
|
+
chunks.push(c);
|
|
21
|
+
});
|
|
22
|
+
res.on('end', () => {
|
|
23
|
+
resolve({
|
|
24
|
+
body: Buffer.concat(chunks).toString('utf8'),
|
|
25
|
+
headers: res.headers,
|
|
26
|
+
statusCode: res.statusCode ?? 0,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
req.on('error', reject);
|
|
31
|
+
if (options.body) {
|
|
32
|
+
req.write(options.body);
|
|
33
|
+
}
|
|
34
|
+
req.end();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export class DevcenterClient {
|
|
38
|
+
baseUrl;
|
|
39
|
+
constructor(baseUrl = getDevcenterBaseUrl()) {
|
|
40
|
+
this.baseUrl = baseUrl;
|
|
41
|
+
}
|
|
42
|
+
async authForm(method, path, token, params) {
|
|
43
|
+
const body = new URLSearchParams(params).toString();
|
|
44
|
+
const res = await nodeRequest(method, this.url(path), {
|
|
45
|
+
body,
|
|
46
|
+
headers: {
|
|
47
|
+
...DEFAULT_HEADERS,
|
|
48
|
+
...basicAuthHeaders(token),
|
|
49
|
+
'Content-Length': String(Buffer.byteLength(body)),
|
|
50
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
let json = {};
|
|
54
|
+
if (res.body) {
|
|
55
|
+
try {
|
|
56
|
+
json = JSON.parse(res.body);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
json = res.body;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { body: json, ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode };
|
|
63
|
+
}
|
|
64
|
+
async checkBrokenLinks(token, content) {
|
|
65
|
+
return this.authForm('POST', brokenLinkChecksPath(), token, { content });
|
|
66
|
+
}
|
|
67
|
+
async getJson(path, query, options) {
|
|
68
|
+
const u = new URL(this.url(path));
|
|
69
|
+
if (query) {
|
|
70
|
+
for (const [k, v] of Object.entries(query)) {
|
|
71
|
+
u.searchParams.set(k, v);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const headers = { ...DEFAULT_HEADERS };
|
|
75
|
+
if (options?.token) {
|
|
76
|
+
Object.assign(headers, basicAuthHeaders(options.token));
|
|
77
|
+
}
|
|
78
|
+
const res = await nodeRequest('GET', u.toString(), { headers });
|
|
79
|
+
const raw = res.body ?? '';
|
|
80
|
+
const trimmed = raw.trim();
|
|
81
|
+
let parsed;
|
|
82
|
+
if (trimmed === '') {
|
|
83
|
+
parsed = {};
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { body: {}, ok: false, status: res.statusCode };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { body: parsed, ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode };
|
|
94
|
+
}
|
|
95
|
+
async head(path) {
|
|
96
|
+
const res = await nodeRequest('HEAD', this.url(path), { headers: { ...DEFAULT_HEADERS } });
|
|
97
|
+
const status = res.statusCode;
|
|
98
|
+
const { location } = res.headers;
|
|
99
|
+
const loc = Array.isArray(location) ? location[0] : location;
|
|
100
|
+
return {
|
|
101
|
+
location: loc,
|
|
102
|
+
notFound: status === 404,
|
|
103
|
+
ok: [200, 201].includes(status),
|
|
104
|
+
redirect: [301, 302].includes(status),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async updateArticle(token, articleId, params) {
|
|
108
|
+
return this.authForm('PUT', updateArticlePath(articleId), token, params);
|
|
109
|
+
}
|
|
110
|
+
async validateArticle(token, articleId, params) {
|
|
111
|
+
return this.authForm('POST', validateArticlePath(articleId), token, params);
|
|
112
|
+
}
|
|
113
|
+
url(path) {
|
|
114
|
+
const base = this.baseUrl.replace(/\/$/, '');
|
|
115
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
116
|
+
return `${base}${p}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=devcenter-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devcenter-client.js","sourceRoot":"","sources":["../../src/lib/devcenter-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev Center private endpoints expect HTTP Basic auth: base64(raw API token), not Bearer.
|
|
3
|
+
* The token itself is resolved by `@heroku-cli/command` (`APIClient.getAuth()` / credential manager).
|
|
4
|
+
*/
|
|
5
|
+
export declare function basicAuthHeaderValue(token: string): string;
|
|
6
|
+
export declare function basicAuthHeaders(token: string): Record<string, string>;
|
|
7
|
+
//# sourceMappingURL=heroku-api-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heroku-api-auth.d.ts","sourceRoot":"","sources":["../../src/lib/heroku-api-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEtE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev Center private endpoints expect HTTP Basic auth: base64(raw API token), not Bearer.
|
|
3
|
+
* The token itself is resolved by `@heroku-cli/command` (`APIClient.getAuth()` / credential manager).
|
|
4
|
+
*/
|
|
5
|
+
export function basicAuthHeaderValue(token) {
|
|
6
|
+
const encoded = Buffer.from(token).toString('base64');
|
|
7
|
+
return `Basic ${encoded}`;
|
|
8
|
+
}
|
|
9
|
+
export function basicAuthHeaders(token) {
|
|
10
|
+
return { Authorization: basicAuthHeaderValue(token) };
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=heroku-api-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heroku-api-auth.js","sourceRoot":"","sources":["../../src/lib/heroku-api-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACrD,OAAO,SAAS,OAAO,EAAE,CAAA;AAC3B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,EAAC,aAAa,EAAE,oBAAoB,CAAC,KAAK,CAAC,EAAC,CAAA;AACrD,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function getDevcenterBaseUrl(): string;
|
|
2
|
+
export declare function articlePath(slug: string): string;
|
|
3
|
+
export declare function articleApiPath(slug: string): string;
|
|
4
|
+
export declare function searchApiPath(): string;
|
|
5
|
+
export declare function validateArticlePath(id: number | string): string;
|
|
6
|
+
export declare function updateArticlePath(id: number | string): string;
|
|
7
|
+
/** Private CMS show by numeric id or slug (same path segment as `update`). */
|
|
8
|
+
export declare function privateArticleShowPath(slugOrId: string): string;
|
|
9
|
+
export declare function brokenLinkChecksPath(): string;
|
|
10
|
+
export declare function articleUrlMatches(url: string, baseUrl: string): boolean;
|
|
11
|
+
export declare function slugFromArticleUrl(url: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Directory used for local `*.md` article files (`pull`, `push`, `preview`).
|
|
14
|
+
* Tests may set `DEVCENTER_CLI_CWD` so commands can run without changing `process.cwd()` away from the plugin root (required for oclif).
|
|
15
|
+
*/
|
|
16
|
+
export declare function getArticleWorkingDirectory(): string;
|
|
17
|
+
export declare function mdFilePath(slug: string): string;
|
|
18
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE7D;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
export function getDevcenterBaseUrl() {
|
|
3
|
+
return process.env.DEVCENTER_BASE_URL ?? 'https://devcenter.heroku.com';
|
|
4
|
+
}
|
|
5
|
+
export function articlePath(slug) {
|
|
6
|
+
return `/articles/${slug}`;
|
|
7
|
+
}
|
|
8
|
+
export function articleApiPath(slug) {
|
|
9
|
+
return `${articlePath(slug)}.json`;
|
|
10
|
+
}
|
|
11
|
+
export function searchApiPath() {
|
|
12
|
+
return '/api/v1/search.json';
|
|
13
|
+
}
|
|
14
|
+
export function validateArticlePath(id) {
|
|
15
|
+
return `/api/v1/private/articles/${id}/validate.json`;
|
|
16
|
+
}
|
|
17
|
+
export function updateArticlePath(id) {
|
|
18
|
+
return `/api/v1/private/articles/${id}.json`;
|
|
19
|
+
}
|
|
20
|
+
/** Private CMS show by numeric id or slug (same path segment as `update`). */
|
|
21
|
+
export function privateArticleShowPath(slugOrId) {
|
|
22
|
+
return `/api/v1/private/articles/${encodeURIComponent(slugOrId)}.json`;
|
|
23
|
+
}
|
|
24
|
+
export function brokenLinkChecksPath() {
|
|
25
|
+
return '/api/v1/private/broken-link-checks.json';
|
|
26
|
+
}
|
|
27
|
+
export function articleUrlMatches(url, baseUrl) {
|
|
28
|
+
const escaped = baseUrl.replaceAll('/', String.raw `\/`);
|
|
29
|
+
return new RegExp(`^${escaped}/articles/.+`).test(url);
|
|
30
|
+
}
|
|
31
|
+
export function slugFromArticleUrl(url) {
|
|
32
|
+
if (!url)
|
|
33
|
+
return url;
|
|
34
|
+
const parts = url.split('/articles/');
|
|
35
|
+
const tail = parts.at(-1) ?? url;
|
|
36
|
+
return tail.split('?')[0].split('#')[0];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Directory used for local `*.md` article files (`pull`, `push`, `preview`).
|
|
40
|
+
* Tests may set `DEVCENTER_CLI_CWD` so commands can run without changing `process.cwd()` away from the plugin root (required for oclif).
|
|
41
|
+
*/
|
|
42
|
+
export function getArticleWorkingDirectory() {
|
|
43
|
+
return process.env.DEVCENTER_CLI_CWD ?? process.cwd();
|
|
44
|
+
}
|
|
45
|
+
export function mdFilePath(slug) {
|
|
46
|
+
return resolve(getArticleWorkingDirectory(), `${slug}.md`);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAEjC,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,8BAA8B,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,aAAa,IAAI,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,qBAAqB,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,EAAmB;IACrD,OAAO,4BAA4B,EAAE,gBAAgB,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAmB;IACnD,OAAO,4BAA4B,EAAE,OAAO,CAAA;AAC9C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,sBAAsB,CAAC,QAAgB;IACrD,OAAO,4BAA4B,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAA;AACxE,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,yCAAyC,CAAA;AAClD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAAe;IAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAA,IAAI,CAAC,CAAA;IACvD,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,CAAA;IACpB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAA;IAChC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAA;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B;IACxC,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,OAAO,CAAC,0BAA0B,EAAE,EAAE,GAAG,IAAI,KAAK,CAAC,CAAA;AAC5D,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Express } from 'express';
|
|
2
|
+
export type PreviewApp = {
|
|
3
|
+
app: Express;
|
|
4
|
+
broadcastReload: () => void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Express app for local Dev Center preview (routes only; no listen).
|
|
8
|
+
* `broadcastReload` notifies active `/stream` SSE connections (same as file save in `runPreview`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function createPreviewApp(): PreviewApp;
|
|
11
|
+
export declare function runPreview(options: {
|
|
12
|
+
host: string;
|
|
13
|
+
mdPath: string;
|
|
14
|
+
port: number;
|
|
15
|
+
slug: string;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=preview-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-server.d.ts","sourceRoot":"","sources":["../../src/lib/preview-server.ts"],"names":[],"mappings":"AAKA,OAAgB,EAAC,KAAK,OAAO,EAAgB,MAAM,SAAS,CAAA;AAW5D,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,OAAO,CAAA;IACZ,eAAe,EAAE,MAAM,IAAI,CAAA;CAC5B,CAAA;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAmD7C;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAmChB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ux } from '@oclif/core';
|
|
2
|
+
import { watch } from 'chokidar';
|
|
3
|
+
import createDebug from 'debug';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
import { ArticleFile } from './article-file.js';
|
|
9
|
+
import { getArticleWorkingDirectory } from './paths.js';
|
|
10
|
+
import { renderNotFoundPage, renderPreviewPage } from './preview-templates.js';
|
|
11
|
+
const dbg = createDebug('devcenter:preview');
|
|
12
|
+
/**
|
|
13
|
+
* Express app for local Dev Center preview (routes only; no listen).
|
|
14
|
+
* `broadcastReload` notifies active `/stream` SSE connections (same as file save in `runPreview`).
|
|
15
|
+
*/
|
|
16
|
+
export function createPreviewApp() {
|
|
17
|
+
const clients = new Set();
|
|
18
|
+
const app = express();
|
|
19
|
+
app.get('/favicon.ico', (_req, res) => {
|
|
20
|
+
res.status(204).end();
|
|
21
|
+
});
|
|
22
|
+
app.get('/stream', (req, res) => {
|
|
23
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
24
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
25
|
+
res.setHeader('Connection', 'keep-alive');
|
|
26
|
+
res.flushHeaders();
|
|
27
|
+
clients.add(res);
|
|
28
|
+
const timer = setInterval(() => {
|
|
29
|
+
res.write(':refreshing \n\n');
|
|
30
|
+
}, 20_000);
|
|
31
|
+
req.on('close', () => {
|
|
32
|
+
clearInterval(timer);
|
|
33
|
+
clients.delete(res);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
app.get('/:articleSlug', (req, res) => {
|
|
37
|
+
const { articleSlug } = req.params;
|
|
38
|
+
dbg(`Local article requested: ${articleSlug}`);
|
|
39
|
+
const srcPath = resolve(getArticleWorkingDirectory(), `${articleSlug}.md`);
|
|
40
|
+
if (!existsSync(srcPath)) {
|
|
41
|
+
const ref = typeof req.get('Referer') === 'string' ? req.get('Referer') : undefined;
|
|
42
|
+
res.status(404).type('html').send(renderNotFoundPage(ref));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
dbg('Parsing');
|
|
46
|
+
const article = ArticleFile.read(srcPath);
|
|
47
|
+
const { html } = renderPreviewPage(article, articleSlug);
|
|
48
|
+
dbg('Serving');
|
|
49
|
+
res.type('html').send(html);
|
|
50
|
+
});
|
|
51
|
+
const broadcastReload = () => {
|
|
52
|
+
for (const clientRes of clients) {
|
|
53
|
+
try {
|
|
54
|
+
clientRes.write('data: reload\n\n');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
clients.delete(clientRes);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return { app, broadcastReload };
|
|
62
|
+
}
|
|
63
|
+
export async function runPreview(options) {
|
|
64
|
+
const { host, mdPath, port, slug } = options;
|
|
65
|
+
const { app, broadcastReload } = createPreviewApp();
|
|
66
|
+
const server = await new Promise(resolveListen => {
|
|
67
|
+
const s = app.listen(port, host, () => {
|
|
68
|
+
resolveListen(s);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
const watcher = watch(mdPath, { ignoreInitial: true }).on('change', () => {
|
|
72
|
+
dbg(`File modified: ${mdPath}`);
|
|
73
|
+
broadcastReload();
|
|
74
|
+
});
|
|
75
|
+
const url = `http://${host}:${port}/${slug}`;
|
|
76
|
+
ux.stdout('');
|
|
77
|
+
ux.stdout(`Live preview for ${slug} available at ${url}`);
|
|
78
|
+
ux.stdout(`It will refresh when you save ${mdPath}`);
|
|
79
|
+
ux.stdout('Press Ctrl+C to exit...');
|
|
80
|
+
await open(url);
|
|
81
|
+
await new Promise(resolveShutdown => {
|
|
82
|
+
const stop = () => {
|
|
83
|
+
watcher.close().then(() => {
|
|
84
|
+
server.close(() => {
|
|
85
|
+
ux.stdout('\nPreview finished.');
|
|
86
|
+
resolveShutdown();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
process.once('SIGINT', stop);
|
|
91
|
+
process.once('SIGTERM', stop);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=preview-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-server.js","sourceRoot":"","sources":["../../src/lib/preview-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ArticleFile } from './article-file.js';
|
|
2
|
+
export declare function renderPreviewPage(article: ArticleFile, slug: string): {
|
|
3
|
+
html: string;
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function renderNotFoundPage(referrerUrl?: string): string;
|
|
7
|
+
//# sourceMappingURL=preview-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-templates.d.ts","sourceRoot":"","sources":["../../src/lib/preview-templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAA;AA6BlD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;CAAC,CAsDpG;AAED,wBAAgB,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAY/D"}
|