piral-cli 0.14.28 → 0.14.31

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 (65) hide show
  1. package/lib/apps/debug-pilet.js +2 -1
  2. package/lib/apps/debug-pilet.js.map +1 -1
  3. package/lib/apps/debug-piral.js +6 -1
  4. package/lib/apps/debug-piral.js.map +1 -1
  5. package/lib/apps/publish-pilet.d.ts +4 -0
  6. package/lib/apps/publish-pilet.js +3 -2
  7. package/lib/apps/publish-pilet.js.map +1 -1
  8. package/lib/apps/publish-piral.d.ts +6 -2
  9. package/lib/apps/publish-piral.js +10 -8
  10. package/lib/apps/publish-piral.js.map +1 -1
  11. package/lib/commands.js +12 -4
  12. package/lib/commands.js.map +1 -1
  13. package/lib/common/browser.d.ts +1 -0
  14. package/lib/common/browser.js +15 -9
  15. package/lib/common/browser.js.map +1 -1
  16. package/lib/common/clients/npm.d.ts +1 -0
  17. package/lib/common/clients/npm.js +7 -1
  18. package/lib/common/clients/npm.js.map +1 -1
  19. package/lib/common/http.d.ts +4 -2
  20. package/lib/common/http.js +35 -16
  21. package/lib/common/http.js.map +1 -1
  22. package/lib/common/info.d.ts +3 -0
  23. package/lib/common/info.js +5 -1
  24. package/lib/common/info.js.map +1 -1
  25. package/lib/common/injectors.d.ts +1 -1
  26. package/lib/common/injectors.js +1 -1
  27. package/lib/common/injectors.js.map +1 -1
  28. package/lib/common/interactive.d.ts +9 -0
  29. package/lib/common/interactive.js +36 -1
  30. package/lib/common/interactive.js.map +1 -1
  31. package/lib/common/log.d.ts +1 -0
  32. package/lib/common/log.js +9 -2
  33. package/lib/common/log.js.map +1 -1
  34. package/lib/common/npm.d.ts +1 -1
  35. package/lib/common/npm.js +8 -3
  36. package/lib/common/npm.js.map +1 -1
  37. package/lib/external/index.js +21984 -24357
  38. package/lib/messages.d.ts +46 -0
  39. package/lib/messages.js +53 -1
  40. package/lib/messages.js.map +1 -1
  41. package/lib/questionnaire.js +7 -4
  42. package/lib/questionnaire.js.map +1 -1
  43. package/lib/release.d.ts +1 -1
  44. package/lib/release.js +35 -3
  45. package/lib/release.js.map +1 -1
  46. package/lib/types/common.d.ts +1 -1
  47. package/package.json +3 -3
  48. package/src/apps/debug-pilet.ts +2 -1
  49. package/src/apps/debug-piral.ts +7 -1
  50. package/src/apps/publish-pilet.ts +8 -1
  51. package/src/apps/publish-piral.ts +34 -10
  52. package/src/commands.ts +12 -4
  53. package/src/common/browser.ts +11 -7
  54. package/src/common/clients/npm.ts +4 -0
  55. package/src/common/http.ts +56 -19
  56. package/src/common/info.ts +5 -1
  57. package/src/common/injectors.ts +1 -1
  58. package/src/common/interactive.test.ts +3 -0
  59. package/src/common/interactive.ts +53 -1
  60. package/src/common/log.ts +9 -1
  61. package/src/common/npm.ts +14 -3
  62. package/src/messages.ts +52 -0
  63. package/src/questionnaire.ts +15 -3
  64. package/src/release.ts +47 -5
  65. package/src/types/common.ts +1 -1
@@ -1,17 +1,14 @@
1
1
  import { join } from 'path';
2
2
  import { Agent } from 'https';
3
3
  import { Stream } from 'stream';
4
- import { platform, tmpdir } from 'os';
4
+ import { tmpdir } from 'os';
5
5
  import { createWriteStream } from 'fs';
6
6
  import { log } from './log';
7
+ import { standardHeaders } from './info';
8
+ import { getTokenInteractively } from './interactive';
7
9
  import { axios, FormData } from '../external';
8
10
  import { PiletPublishScheme } from '../types';
9
11
 
10
- const os = platform();
11
- const standardHeaders = {
12
- 'user-agent': `piral-cli/http.node-${os}`,
13
- };
14
-
15
12
  function getMessage(body: string | { message?: string }) {
16
13
  if (typeof body === 'string') {
17
14
  try {
@@ -60,27 +57,35 @@ export function downloadFile(target: string, ca?: Buffer): Promise<Array<string>
60
57
  });
61
58
  }
62
59
 
63
- export interface PostFileResult {
60
+ export interface PostFormResult {
64
61
  status: number;
65
62
  success: boolean;
66
63
  response?: object;
67
64
  }
68
65
 
69
- export function postFile(
66
+ export type FormDataObj = Record<string, string | [Buffer, string]>;
67
+
68
+ export function postForm(
70
69
  target: string,
71
70
  scheme: PiletPublishScheme,
72
71
  key: string,
73
- file: Buffer,
74
- customFields: Record<string, string> = {},
72
+ formData: FormDataObj,
75
73
  customHeaders: Record<string, string> = {},
76
74
  ca?: Buffer,
77
- ): Promise<PostFileResult> {
78
- const form = new FormData();
75
+ interactive = false,
76
+ ): Promise<PostFormResult> {
79
77
  const httpsAgent = ca ? new Agent({ ca }) : undefined;
78
+ const form = new FormData();
80
79
 
81
- Object.keys(customFields).forEach((key) => form.append(key, customFields[key]));
80
+ Object.keys(formData).forEach((key) => {
81
+ const value = formData[key];
82
82
 
83
- form.append('file', file, 'pilet.tgz');
83
+ if (typeof value === 'string') {
84
+ form.append(key, value);
85
+ } else {
86
+ form.append(key, value[0], value[1]);
87
+ }
88
+ });
84
89
 
85
90
  const headers: Record<string, string> = {
86
91
  ...form.getHeaders(),
@@ -114,16 +119,34 @@ export function postFile(
114
119
  maxBodyLength: Infinity,
115
120
  })
116
121
  .then(
117
- (res) => ({
118
- status: res.status,
119
- success: true,
120
- response: res.data,
121
- }),
122
+ (res) => {
123
+ return {
124
+ status: res.status,
125
+ success: true,
126
+ response: res.data,
127
+ };
128
+ },
122
129
  (error) => {
123
130
  if (error.response) {
124
131
  // The request was made and the server responded with a status code
125
132
  // that falls out of the range of 2xx
126
133
  const { data, statusText, status } = error.response;
134
+
135
+ if (interactive && 'interactiveAuth' in data) {
136
+ const { interactiveAuth } = data;
137
+
138
+ if (typeof interactiveAuth === 'string') {
139
+ log(
140
+ 'generalDebug_0003',
141
+ `Received status "${status}" from HTTP - trying interactive log in to "${interactiveAuth}".`,
142
+ );
143
+
144
+ return getTokenInteractively(interactiveAuth, httpsAgent).then(({ mode, token }) =>
145
+ postForm(target, mode, token, formData, customHeaders, ca, false),
146
+ );
147
+ }
148
+ }
149
+
127
150
  const message = getMessage(data) || '';
128
151
  log('unsuccessfulHttpPost_0066', statusText, status, message);
129
152
  return {
@@ -164,3 +187,17 @@ export function postFile(
164
187
  },
165
188
  );
166
189
  }
190
+
191
+ export function postFile(
192
+ target: string,
193
+ scheme: PiletPublishScheme,
194
+ key: string,
195
+ file: Buffer,
196
+ customFields: Record<string, string> = {},
197
+ customHeaders: Record<string, string> = {},
198
+ ca?: Buffer,
199
+ interactive = false,
200
+ ): Promise<PostFormResult> {
201
+ const data: FormDataObj = { ...customFields, file: [file, 'pilet.tgz'] };
202
+ return postForm(target, scheme, key, data, customHeaders, ca, interactive);
203
+ }
@@ -1,6 +1,7 @@
1
- import { cpus } from 'os';
1
+ import { cpus, platform } from 'os';
2
2
 
3
3
  const info = require('../../package.json');
4
+ const os = platform();
4
5
 
5
6
  export function findCompatVersion(version: string) {
6
7
  // we only care about major and minor
@@ -22,3 +23,6 @@ export const repositoryUrl = info.repository.url;
22
23
  export const isWindows = process.platform === 'win32';
23
24
  export const pathSeparator = isWindows ? ';' : ':';
24
25
  export const cpuCount = cpus().length;
26
+ export const standardHeaders = {
27
+ 'user-agent': `piral-cli/http.node-${os}`,
28
+ };
@@ -17,8 +17,8 @@ export function notifyServerOnline(bundlers: Array<Bundler>, path: string, api:
17
17
 
18
18
  export function createInitialKrasConfig(
19
19
  directory: string,
20
- map: Record<string, string> = {},
21
20
  sources: Array<string> = [],
21
+ map: Record<string, string> = {},
22
22
  feed: string | Array<string> = [],
23
23
  ) {
24
24
  return {
@@ -3,6 +3,9 @@ import { promptConfirm, promptSelect } from './interactive';
3
3
  const answer = 'Yes, really';
4
4
 
5
5
  jest.mock('../external', () => ({
6
+ rc(_, cfg) {
7
+ return cfg;
8
+ },
6
9
  inquirer: {
7
10
  prompt: (...any) => {
8
11
  return Promise.resolve({ q: answer });
@@ -1,4 +1,9 @@
1
- import { inquirer } from '../external';
1
+ import { Agent } from 'https';
2
+ import { openBrowserAt } from './browser';
3
+ import { standardHeaders } from './info';
4
+ import { logSuspend } from './log';
5
+ import { axios, inquirer } from '../external';
6
+ import { PiletPublishScheme } from '../types';
2
7
 
3
8
  export function promptSelect(message: string, values: Array<string>, defaultValue: string): Promise<string> {
4
9
  const questions = [
@@ -24,3 +29,50 @@ export function promptConfirm(message: string, defaultValue: boolean): Promise<b
24
29
  ];
25
30
  return inquirer.prompt(questions).then((answers: any) => answers.q);
26
31
  }
32
+
33
+ type TokenResult = Promise<{ mode: PiletPublishScheme; token: string }>;
34
+
35
+ const tokenRetrievers: Record<string, TokenResult> = {};
36
+
37
+ export function getTokenInteractively(url: string, httpsAgent: Agent): TokenResult {
38
+ if (!(url in tokenRetrievers)) {
39
+ const logResume = logSuspend();
40
+
41
+ tokenRetrievers[url] = axios.default
42
+ .post(
43
+ url,
44
+ {
45
+ clientId: 'piral-cli',
46
+ clientName: 'Piral CLI',
47
+ description: 'Authorize the Piral CLI temporarily to perform actions in your name.',
48
+ },
49
+ {
50
+ headers: {
51
+ ...standardHeaders,
52
+ 'content-type': 'application/json',
53
+ },
54
+ httpsAgent,
55
+ },
56
+ )
57
+ .then((res) => {
58
+ const { loginUrl, callbackUrl, expires } = res.data;
59
+ const now = new Date();
60
+ const then = new Date(expires);
61
+ const diff = ~~((then.valueOf() - now.valueOf()) / (60 * 1000));
62
+
63
+ console.log(`Use the URL below to complete the login. The link expires in ${diff} minutes (${then}).`);
64
+ console.log('===');
65
+ console.log(loginUrl);
66
+ console.log('===');
67
+
68
+ openBrowserAt(loginUrl);
69
+
70
+ return axios.default
71
+ .get(callbackUrl)
72
+ .then(({ data }) => ({ ...data }))
73
+ .finally(logResume);
74
+ });
75
+ }
76
+
77
+ return tokenRetrievers[url];
78
+ }
package/src/common/log.ts CHANGED
@@ -8,6 +8,7 @@ import { LogLevels, QuickMessage } from '../types';
8
8
 
9
9
  type Messages = typeof messages;
10
10
  type MessageTypes = keyof Messages;
11
+ let currentProgress: string = undefined;
11
12
 
12
13
  const logger = (() => {
13
14
  try {
@@ -88,7 +89,8 @@ export function logFail(message: string, ...args: Array<string | number | boolea
88
89
  }
89
90
 
90
91
  export function progress(message: string, ...args: Array<string | number | boolean>) {
91
- logger.progress(format(message, ...args));
92
+ currentProgress = format(message, ...args);
93
+ logger.progress(currentProgress);
92
94
  }
93
95
 
94
96
  export function logReset() {
@@ -96,6 +98,12 @@ export function logReset() {
96
98
  logger.stopSpinner();
97
99
  }
98
100
 
101
+ export function logSuspend() {
102
+ logReset();
103
+
104
+ return () => logger.progress(currentProgress);
105
+ }
106
+
99
107
  export function fail<T extends MessageTypes>(type: T, ...args: Parameters<Messages[T]>): never {
100
108
  const message = log(type, ...args);
101
109
  const error = new Error(message);
package/src/common/npm.ts CHANGED
@@ -164,9 +164,20 @@ export function initNpmProject(client: NpmClientType, projectName: string, targe
164
164
  return initProject(projectName, target);
165
165
  }
166
166
 
167
- export function publishNpmPackage(target = '.', file = '*.tgz', flags: Array<string> = []): Promise<string> {
168
- const { publishPackage } = clients.npm;
169
- return publishPackage(target, file, ...flags);
167
+ export function publishNpmPackage(
168
+ target = '.',
169
+ file = '*.tgz',
170
+ flags: Array<string> = [],
171
+ interactive = false,
172
+ ): Promise<string> {
173
+ const { publishPackage, loginUser } = clients.npm;
174
+ return publishPackage(target, file, ...flags).catch((err) => {
175
+ if (!interactive) {
176
+ throw err;
177
+ }
178
+
179
+ return loginUser().then(() => publishNpmPackage(target, file, flags, false));
180
+ });
170
181
  }
171
182
 
172
183
  export function createNpmPackage(target = '.'): Promise<string> {
package/src/messages.ts CHANGED
@@ -2088,6 +2088,58 @@ export function publishEmulatorSourcesInvalid_0114(): QuickMessage {
2088
2088
  ];
2089
2089
  }
2090
2090
 
2091
+ /**
2092
+ * @kind Error
2093
+ *
2094
+ * @summary
2095
+ * The "feed" provider requires a "--opts.url" argument.
2096
+ *
2097
+ * @abstract
2098
+ * The `piral publish --type release` command requires the selection of a suitable
2099
+ * provider for running successfully. The "feed" provider releases the files to
2100
+ * the a Piral Feed Service with the static page feature.
2101
+ *
2102
+ * Make sure to supply the URL for the feed service via the `--opts.url` command
2103
+ * line flag.
2104
+ *
2105
+ * @example
2106
+ * The following command would specify `https://feed.piral.cloud/api/v1/feed/sample/page`
2107
+ * for the feed service:
2108
+ *
2109
+ * ```sh
2110
+ * piral publish --type release --provider feed --opts.url "https://feed.piral.cloud/api/v1/feed/sample/page" --opts.apikey "foobar123"
2111
+ * ```
2112
+ */
2113
+ export function publishFeedMissingUrl_0115(): QuickMessage {
2114
+ return [LogLevels.error, '0115', `The "feed" provider requires a "--opts.url" argument.`];
2115
+ }
2116
+
2117
+ /**
2118
+ * @kind Error
2119
+ *
2120
+ * @summary
2121
+ * The "feed" provider requires a "--opts.version" argument.
2122
+ *
2123
+ * @abstract
2124
+ * The `piral publish --type release` command requires the selection of a suitable
2125
+ * provider for running successfully. The "feed" provider releases the files to
2126
+ * the a Piral Feed Service with the static page feature.
2127
+ *
2128
+ * Make sure to supply the version either explicitly via the `--opts.version` argument
2129
+ * or implicitly by having the artifacts stored in a sub-directory of the project's root,
2130
+ * which contains a package.json with the version to use.
2131
+ *
2132
+ * @example
2133
+ * The following command would specify version "1.2.3" for the feed service:
2134
+ *
2135
+ * ```sh
2136
+ * piral publish --type release --provider feed --opts.url "..." --opts.apikey "..." --opts.version "1.2.3"
2137
+ * ```
2138
+ */
2139
+ export function publishFeedMissingVersion_0116(): QuickMessage {
2140
+ return [LogLevels.error, '0116', `The "feed" provider requires either a "--opts.version" argument or a package.json with a version.`];
2141
+ }
2142
+
2091
2143
  /**
2092
2144
  * @kind Warning
2093
2145
  *
@@ -7,6 +7,7 @@ type FlagType = 'string' | 'number' | 'boolean' | 'object';
7
7
  interface Flag {
8
8
  name: string;
9
9
  type?: FlagType;
10
+ alias: Array<string>;
10
11
  values?: Array<any>;
11
12
  describe?: string;
12
13
  default?: any;
@@ -16,16 +17,23 @@ interface Flag {
16
17
  function getCommandData(retrieve: any) {
17
18
  const instructions: Array<Flag> = [];
18
19
  const fn = {
20
+ alias(name: string, altName: string) {
21
+ return this.swap(name, (flag) => ({
22
+ ...flag,
23
+ alias: [...flag.alias, altName],
24
+ }));
25
+ },
19
26
  positional(name: string, info: Flag) {
20
27
  instructions.push({
21
28
  ...info,
29
+ alias: [],
22
30
  name,
23
31
  });
24
32
  return this;
25
33
  },
26
34
  swap(name: string, swapper: (flag: Flag) => Flag) {
27
35
  const [flag] = instructions.filter((m) => m.name === name);
28
- const newFlag = swapper(flag || { name });
36
+ const newFlag = swapper(flag || { name, alias: [] });
29
37
 
30
38
  if (!flag) {
31
39
  instructions.push(newFlag);
@@ -135,7 +143,7 @@ export function runQuestionnaireFor(
135
143
  const questions = instructions
136
144
  .filter((instruction) => !ignored.includes(instruction.name))
137
145
  .filter((instruction) => !acceptAll || (instruction.default === undefined && instruction.required))
138
- .filter((instruction) => args[instruction.name] === undefined)
146
+ .filter((instruction) => [...instruction.alias, instruction.name].every((m) => args[m] === undefined))
139
147
  .filter((instruction) => instruction.type !== 'object')
140
148
  .map((instruction) => ({
141
149
  name: instruction.name,
@@ -146,12 +154,16 @@ export function runQuestionnaireFor(
146
154
  validate: instruction.type === 'number' ? (input: string) => !isNaN(+input) : () => true,
147
155
  }));
148
156
 
157
+
149
158
  return inquirer.prompt(questions).then((answers) => {
150
159
  const parameters: any = {};
151
160
 
152
161
  for (const instruction of instructions) {
153
162
  const name = instruction.name;
154
- const value = answers[name] ?? ignoredInstructions[name] ?? args[name];
163
+ const value =
164
+ answers[name] ??
165
+ ignoredInstructions[name] ??
166
+ [...instruction.alias, instruction.name].map((m) => args[m]).find((v) => v !== undefined);
155
167
  parameters[name] = value !== undefined ? getValue(instruction.type, value as any) : instruction.default;
156
168
  }
157
169
 
package/src/release.ts CHANGED
@@ -1,8 +1,19 @@
1
- import { basename, resolve } from 'path';
2
- import { copy, fail } from './common';
1
+ import { basename, dirname, relative, resolve } from 'path';
2
+ import { copy, fail, findFile, FormDataObj, postForm, readBinary } from './common';
3
3
  import { availableReleaseProviders } from './helpers';
4
4
  import { ReleaseProvider } from './types';
5
5
 
6
+ async function getVersion(directory: string) {
7
+ const data = await findFile(directory, 'package.json');
8
+
9
+ if (!data) {
10
+ fail('packageJsonNotFound_0020');
11
+ }
12
+
13
+ const { version } = require(data);
14
+ return version;
15
+ }
16
+
6
17
  export interface QualifiedReleaseProvider {
7
18
  name: string;
8
19
  action: ReleaseProvider;
@@ -12,7 +23,7 @@ const providers: Record<string, ReleaseProvider> = {
12
23
  none() {
13
24
  return Promise.resolve();
14
25
  },
15
- async xcopy(files, args) {
26
+ async xcopy(_, files, args) {
16
27
  const { target } = args;
17
28
 
18
29
  if (!target) {
@@ -21,6 +32,31 @@ const providers: Record<string, ReleaseProvider> = {
21
32
 
22
33
  await Promise.all(files.map(async (file) => copy(file, resolve(target, basename(file)))));
23
34
  },
35
+ async feed(directory, files, args, interactive) {
36
+ const { url, apiKey, scheme = 'basic', version = await getVersion(directory) } = args;
37
+
38
+ if (!url) {
39
+ fail('publishFeedMissingUrl_0115');
40
+ }
41
+
42
+ if (!version) {
43
+ fail('publishFeedMissingVersion_0116');
44
+ }
45
+
46
+ const data: FormDataObj = {
47
+ version,
48
+ type: 'custom',
49
+ };
50
+
51
+ for (const file of files) {
52
+ const relPath = relative(directory, file);
53
+ const fileName = basename(file);
54
+ const content = await readBinary(dirname(file), fileName);
55
+ data[relPath] = [content, fileName];
56
+ }
57
+
58
+ await postForm(url, scheme as any, apiKey, data, {}, undefined, interactive);
59
+ },
24
60
  };
25
61
 
26
62
  function findReleaseProvider(providerName: string) {
@@ -43,7 +79,13 @@ export function setReleaseProvider(provider: QualifiedReleaseProvider) {
43
79
  }
44
80
  }
45
81
 
46
- export function publishArtifacts(providerName: string, files: Array<string>, args: Record<string, string>) {
82
+ export function publishArtifacts(
83
+ providerName: string,
84
+ directory: string,
85
+ files: Array<string>,
86
+ args: Record<string, string>,
87
+ interactive: boolean,
88
+ ) {
47
89
  const runRelease = findReleaseProvider(providerName);
48
- return runRelease(files, args);
90
+ return runRelease(directory, files, args, interactive);
49
91
  }
@@ -42,7 +42,7 @@ export interface Bundler {
42
42
  }
43
43
 
44
44
  export interface ReleaseProvider {
45
- (files: Array<string>, args: Record<string, string>): Promise<void>;
45
+ (directory: string, files: Array<string>, args: Record<string, string>, interactive: boolean): Promise<void>;
46
46
  }
47
47
 
48
48
  export interface TemplateFileLocation {