@newly/cli 0.2.201 → 0.2.203
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.
|
@@ -7,7 +7,7 @@ export function releaseControlRequest(file) {
|
|
|
7
7
|
const value = JSON.parse(readFileSync(file, 'utf8'));
|
|
8
8
|
if (!value || typeof value !== 'object' || Array.isArray(value) || typeof value.reason !== 'string' || !value.reason.trim()
|
|
9
9
|
|| !Number.isSafeInteger(value.expectedRevision) || Number(value.expectedRevision) < 0
|
|
10
|
-
|| !['bootstrap', 'enroll', 'announce', 'override', 'pause', 'resume', 'rollback', 'defer'].includes(String(value.action))) {
|
|
10
|
+
|| !['bootstrap', 'enroll', 'announce', 'override', 'record-activation', 'pause', 'resume', 'rollback', 'defer'].includes(String(value.action))) {
|
|
11
11
|
throw new Error('Request needs action, reason and expectedRevision from the current release-control status');
|
|
12
12
|
}
|
|
13
13
|
return value;
|
|
@@ -21,6 +21,6 @@ export function releaseText(channels) {
|
|
|
21
21
|
const channel = channels.find(item => item.channel === name);
|
|
22
22
|
return channel
|
|
23
23
|
? `${name.padEnd(13)} ${channel.version} r${channel.revision} by ${channel.promotedBy} at ${channel.promotedAt}${channel.digest ? ` ${channel.digest}` : ''}`
|
|
24
|
-
: `${name.padEnd(13)} unset
|
|
25
|
-
}).join('\n');
|
|
24
|
+
: `${name.padEnd(13)} unset`;
|
|
25
|
+
}).join('\n') + '\nRetired channels: expectations and targets come only from `runcloud fleet release-control status`.';
|
|
26
26
|
}
|
|
@@ -334,13 +334,13 @@ export function registerRunCloudFleet(program) {
|
|
|
334
334
|
requireFleetReadAccess(result.coverage.reason);
|
|
335
335
|
console.log(JSON.stringify({ coverage: result.coverage, items: result.items.map(eventEvidence) }, null, 2));
|
|
336
336
|
}));
|
|
337
|
-
fleet.command('releases').description('Show the serve-sim release
|
|
337
|
+
fleet.command('releases').description('Show the retired serve-sim release channels as history; targets live in `fleet release-control status` (admin read)')
|
|
338
338
|
.option('--json', 'Structured channel records')
|
|
339
339
|
.action(opts => action(async () => {
|
|
340
340
|
const { channels, unavailable } = await listReleaseChannels(credentialsClient());
|
|
341
341
|
console.log(opts.json ? JSON.stringify({ unavailable, channels }, null, 2) : `${unavailable ? 'Warning: release storage was partly unavailable\n' : ''}${releaseText(channels)}`);
|
|
342
342
|
}));
|
|
343
|
-
fleet.command('promote <channel> <version>').description('
|
|
343
|
+
fleet.command('promote <channel> <version>').description('Retired: the API refuses channel promotions. Announce a rollout or record an activation with `fleet release-control apply`')
|
|
344
344
|
.requiredOption('--reason <text>', 'Why this build is promoted; kept in the audit history')
|
|
345
345
|
.option('--digest <sha256>', 'Artifact content digest, sha256:<64 hex>').option('--artifact <ref>', 'Where the artifact comes from, e.g. gh-run:<id>')
|
|
346
346
|
.action((channel, version, opts) => action(async () => {
|
package/dist/commands/support.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { ApiClient, friendlyApiError } from '../api.js';
|
|
4
4
|
import { requireCredentials, resolveProject } from '../config.js';
|
|
5
5
|
import { SUPPORT_CHAT_TSX } from './supportChat.generated.js';
|
|
6
6
|
export const SUPPORT_CONFIG = 'src/lib/newly-support.json';
|
|
7
|
+
export const SUPPORT_ARTICLES_DIR = 'support/articles';
|
|
7
8
|
export const SUPPORT_CHAT = 'src/components/SupportChat.tsx';
|
|
8
9
|
export function writeSupportChat(dir, force = false) {
|
|
9
10
|
const path = join(dir, SUPPORT_CHAT);
|
|
@@ -104,6 +105,64 @@ export async function runArticlesList(client, projectId, log) {
|
|
|
104
105
|
for (const a of articles)
|
|
105
106
|
log(`${a.id} ${a.title} (updated ${a.updated_at})`);
|
|
106
107
|
}
|
|
108
|
+
const slugOf = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60).replace(/-+$/, '') || 'article';
|
|
109
|
+
const INDEX = 'articles.json';
|
|
110
|
+
function exportedFiles(dir) {
|
|
111
|
+
try {
|
|
112
|
+
const { articles } = JSON.parse(readFileSync(join(dir, INDEX), 'utf8'));
|
|
113
|
+
return (articles ?? []).map((a) => a.file).filter((f) => typeof f === 'string' && /^[a-z0-9-]+\.md$/.test(f));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export const WEBSITE_ARTICLES = 'support-articles.json';
|
|
120
|
+
export async function runArticlesExport(client, projectId, dir, log, websiteDir) {
|
|
121
|
+
const { settings } = (await client.get(`${base(projectId)}/settings`));
|
|
122
|
+
const websiteCopy = websiteDir ? join(websiteDir, WEBSITE_ARTICLES) : null;
|
|
123
|
+
if (!settings?.enabled) {
|
|
124
|
+
if (websiteCopy && existsSync(websiteCopy))
|
|
125
|
+
unlinkSync(websiteCopy);
|
|
126
|
+
log('Nothing exported: the help chat is off for this app. Turn it on with newly support enable first.');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const { articles } = (await client.get(`${base(projectId)}/articles`));
|
|
130
|
+
const sorted = [...articles].sort((a, b) => a.title.localeCompare(b.title) || a.id.localeCompare(b.id));
|
|
131
|
+
const owned = new Set(exportedFiles(dir));
|
|
132
|
+
const taken = new Set();
|
|
133
|
+
const free = (file) => !taken.has(file) && (owned.has(file) || !existsSync(join(dir, file)));
|
|
134
|
+
const entries = sorted.map((a) => {
|
|
135
|
+
const slug = slugOf(a.title);
|
|
136
|
+
let file = `${slug}.md`;
|
|
137
|
+
for (let n = 2; !free(file); n++)
|
|
138
|
+
file = `${slug}-${n}.md`;
|
|
139
|
+
taken.add(file);
|
|
140
|
+
return {
|
|
141
|
+
id: a.id, file, title: a.title, body: a.body, source: a.source ?? 'manual',
|
|
142
|
+
source_name: a.source_name ?? null, source_url: a.source_url ?? null, updated_at: a.updated_at,
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
mkdirSync(dir, { recursive: true });
|
|
146
|
+
for (const stale of owned) {
|
|
147
|
+
if (!taken.has(stale) && existsSync(join(dir, stale)))
|
|
148
|
+
unlinkSync(join(dir, stale));
|
|
149
|
+
}
|
|
150
|
+
for (const e of entries) {
|
|
151
|
+
const header = [`id: ${e.id}`, `title: ${JSON.stringify(e.title)}`, `source: ${e.source}`];
|
|
152
|
+
if (e.source_name)
|
|
153
|
+
header.push(`source_name: ${JSON.stringify(e.source_name)}`);
|
|
154
|
+
if (e.source_url)
|
|
155
|
+
header.push(`source_url: ${JSON.stringify(e.source_url)}`);
|
|
156
|
+
header.push(`updated_at: ${e.updated_at}`);
|
|
157
|
+
writeFileSync(join(dir, e.file), `---\n${header.join('\n')}\n---\n\n${e.body.trimEnd()}\n`);
|
|
158
|
+
}
|
|
159
|
+
const note = 'Exported from Newly customer support. Edit articles in Newly; newly support articles export overwrites this folder.';
|
|
160
|
+
const index = `${JSON.stringify({ note, articles: entries }, null, 2)}\n`;
|
|
161
|
+
writeFileSync(join(dir, INDEX), index);
|
|
162
|
+
if (websiteDir && websiteCopy && existsSync(websiteDir))
|
|
163
|
+
writeFileSync(websiteCopy, index);
|
|
164
|
+
log(`Exported ${entries.length} ${entries.length === 1 ? 'article' : 'articles'} to ${dir}`);
|
|
165
|
+
}
|
|
107
166
|
const bodyOf = (opts) => {
|
|
108
167
|
if (opts.file)
|
|
109
168
|
return readFileSync(opts.file, 'utf8');
|
|
@@ -266,6 +325,12 @@ export function registerSupport(program) {
|
|
|
266
325
|
const c = ctx(o.project);
|
|
267
326
|
await runArticleImportUrl(c.client, c.projectId, url, Boolean(o.refresh), log);
|
|
268
327
|
}));
|
|
328
|
+
articles.command('export').description('Write the articles into the repo (markdown plus articles.json) for the website FAQ')
|
|
329
|
+
.option('--dir <dir>', 'output folder', SUPPORT_ARTICLES_DIR).option('--project <id>')
|
|
330
|
+
.action((o) => run(async () => {
|
|
331
|
+
const c = ctx(o.project);
|
|
332
|
+
await runArticlesExport(c.client, c.projectId, resolve(o.dir), log, resolve('website'));
|
|
333
|
+
}));
|
|
269
334
|
articles.command('remove <id>').option('--project <id>').action((id, o) => run(async () => {
|
|
270
335
|
const c = ctx(o.project);
|
|
271
336
|
await runArticleRemove(c.client, c.projectId, id, log);
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.2.
|
|
1
|
+
export const CLI_VERSION = '0.2.203';
|