Compare commits

...

7 Commits

Author SHA1 Message Date
Ana Trajkovska
c89cf6fd57 Publish Canary
- now@18.0.1-canary.5
 - @now/node@1.5.2-canary.2
2020-04-20 14:54:32 +02:00
Ana Trajkovska
b7ad18425f [now-cli] Implement pagination for listing secrets (#4084)
* Implement pagination for listing secrets

* Fix tests

* Improvements

* Fix removing a secret
2020-04-20 14:52:18 +02:00
Nathan Rajlich
5554b2d004 Publish Canary
- now@18.0.1-canary.4
2020-04-17 18:21:55 -07:00
Nathan Rajlich
e4ce0d6802 Publish Canary
- @now/node@1.5.2-canary.1
2020-04-17 18:16:17 -07:00
Nathan Rajlich
f477ee6e3b [now-node] Throw an error if "exit" event happens in dev server (#4095)
If the process exits before the "message" event is sent, then the dev
server crashed upon bootup. Often times caused by a missing dependency.
2020-04-18 01:15:43 +00:00
Nathan Rajlich
59b4029a0c [now-cli] Handle errors from startDevServer() in now dev (#4094) 2020-04-18 00:51:14 +00:00
Nathan Rajlich
8411b53fa3 [now-cli] Use entrypoint instead of src for startDevServer() (#4093)
Otherwise the `src` is modified to not have the file extension when used
with zero-config API routes.
2020-04-17 15:01:44 -07:00
11 changed files with 195 additions and 35 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "18.0.1-canary.3",
"version": "18.0.1-canary.5",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",

View File

@@ -2,7 +2,6 @@ import chalk from 'chalk';
import table from 'text-table';
import mri from 'mri';
import ms from 'ms';
import plural from 'pluralize';
import strlen from '../util/strlen.ts';
import { handleError, error } from '../util/error';
import NowSecrets from '../util/secrets';
@@ -12,6 +11,8 @@ import Client from '../util/client.ts';
import getScope from '../util/get-scope.ts';
import createOutput from '../util/output';
import confirm from '../util/input/confirm';
import getCommandFlags from '../util/get-command-flags';
import cmd from '../util/output/cmd.ts';
const help = () => {
console.log(`
@@ -38,6 +39,7 @@ const help = () => {
'TOKEN'
)} Login token
-S, --scope Set a custom scope
-N, --next Show next page of results
${chalk.dim('Examples:')}
@@ -60,6 +62,12 @@ const help = () => {
)} symbol)
${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold('@my-secret')}`)}
${chalk.gray('')} Paginate results, where ${chalk.dim(
'`1584722256178`'
)} is the time in milliseconds since the UNIX epoch.
${chalk.cyan(`$ now secrets ls --next 1584722256178`)}
`);
};
@@ -68,6 +76,7 @@ let argv;
let debug;
let apiUrl;
let subcommand;
let nextTimestamp;
const main = async ctx => {
argv = mri(ctx.argv.slice(2), {
@@ -76,6 +85,7 @@ const main = async ctx => {
help: 'h',
debug: 'd',
yes: 'y',
next: 'N',
},
});
@@ -84,6 +94,7 @@ const main = async ctx => {
debug = argv.debug;
apiUrl = ctx.apiUrl;
subcommand = argv._[0];
nextTimestamp = argv.next;
if (argv.help || !subcommand) {
help();
@@ -132,7 +143,7 @@ async function run({ output, token, contextName, currentTeam, ctx }) {
const start = Date.now();
if (subcommand === 'ls' || subcommand === 'list') {
if (args.length !== 0) {
if (args.length > 1) {
console.error(
error(
`Invalid number of arguments. Usage: ${chalk.cyan('`now secret ls`')}`
@@ -141,11 +152,11 @@ async function run({ output, token, contextName, currentTeam, ctx }) {
return exit(1);
}
const list = await secrets.ls();
const elapsed = ms(new Date() - start);
const { secrets: list, pagination } = await secrets.ls(nextTimestamp);
const elapsed = ms(Date.now() - start);
console.log(
`${plural('secret', list.length, true)} found under ${chalk.bold(
`${list.length > 0 ? 'Secrets' : 'No secrets'} found under ${chalk.bold(
contextName
)} ${chalk.gray(`[${elapsed}]`)}`
);
@@ -172,6 +183,19 @@ async function run({ output, token, contextName, currentTeam, ctx }) {
console.log(`\n${out}\n`);
}
}
if (pagination && pagination.count === 20) {
const prefixedArgs = getPrefixedFlags(argv);
const flags = getCommandFlags(prefixedArgs, [
'_',
'--next',
'-N',
'-d',
'-y',
]);
const nextCmd = `now secrets ${subcommand}${flags} --next ${pagination.next}`;
output.log(`To display the next page run ${cmd(nextCmd)}`);
}
return secrets.close();
}
@@ -186,8 +210,8 @@ async function run({ output, token, contextName, currentTeam, ctx }) {
);
return exit(1);
}
const list = await secrets.ls();
const theSecret = list.find(secret => secret.name === args[0]);
const theSecret = await secrets.getSecretByNameOrId(args[0]);
if (theSecret) {
const yes =
@@ -325,3 +349,28 @@ async function readConfirmation(output, secret, contextName) {
return confirm(`${chalk.bold.red('Are you sure?')}`, false);
}
/**
* This function adds a prefix `-` or `--` to the flags
* passed from the command line, because the package `mri`
* used to extract the args removes them for some reason.
*/
function getPrefixedFlags(args) {
const prefixedArgs = {};
for (const arg in args) {
if (arg === '_') {
prefixedArgs[arg] = argv[arg];
} else {
let prefix = '-';
// Full form flags need two dashes, whereas one letter
// flags need only one.
if (arg.length > 1) {
prefix = '--';
}
prefixedArgs[`${prefix}${arg}`] = argv[arg];
}
}
return prefixedArgs;
}

View File

@@ -28,6 +28,7 @@ import which from 'which';
import {
Builder,
Env,
StartDevServerResult,
FileFsRef,
PackageJson,
detectBuilders,
@@ -134,6 +135,7 @@ export default class DevServer {
private devCommand?: string;
private devProcess?: ChildProcess;
private devProcessPort?: number;
private devServerPids: Set<number>;
private getNowConfigPromise: Promise<NowConfig> | null;
private blockingBuildsPromise: Promise<void> | null;
@@ -154,7 +156,7 @@ export default class DevServer {
this.cachedNowConfig = null;
this.apiDir = null;
this.apiExtensions = new Set<string>();
this.apiExtensions = new Set();
this.server = http.createServer(this.devServerHandler);
this.server.timeout = 0; // Disable timeout
this.serverUrlPrinted = false;
@@ -174,6 +176,8 @@ export default class DevServer {
this.podId = Math.random()
.toString(32)
.slice(-5);
this.devServerPids = new Set();
}
async exit(code = 1) {
@@ -869,6 +873,7 @@ export default class DevServer {
*/
async stop(exitCode?: number): Promise<void> {
const { devProcess } = this;
const { debug, log } = this.output;
if (this.stopping) return;
this.stopping = true;
@@ -876,7 +881,7 @@ export default class DevServer {
if (this.serverUrlPrinted) {
// This makes it look cleaner
process.stdout.write('\n');
this.output.log(`Stopping ${chalk.bold('`now dev`')} server`);
log(`Stopping ${chalk.bold('`now dev`')} server`);
}
const ops: Promise<void>[] = [];
@@ -905,15 +910,29 @@ export default class DevServer {
ops.push(close(this.server));
if (this.watcher) {
this.output.debug(`Closing file watcher`);
debug(`Closing file watcher`);
this.watcher.close();
}
if (this.updateBuildersPromise) {
this.output.debug(`Waiting for builders update to complete`);
debug(`Waiting for builders update to complete`);
ops.push(this.updateBuildersPromise);
}
for (const pid of this.devServerPids) {
debug(`Killing builder dev server with PID ${pid}`);
ops.push(
treeKill(pid).then(
() => {
debug(`Killed builder dev server with PID ${pid}`);
},
(err: Error) => {
debug(`Failed to kill builder dev server with PID ${pid}: ${err}`);
}
)
);
}
try {
await Promise.all(ops);
} catch (err) {
@@ -1451,29 +1470,60 @@ export default class DevServer {
// server child process.
const { builder, package: builderPkg } = match.builderWithPkg;
if (typeof builder.startDevServer === 'function') {
const devServerResult = await builder.startDevServer({
entrypoint: match.src,
workPath: this.cwd,
});
let devServerResult: StartDevServerResult = null;
try {
devServerResult = await builder.startDevServer({
entrypoint: match.entrypoint,
workPath: this.cwd,
});
} catch (err) {
// `starDevServer()` threw an error. Most likely this means the dev
// server process exited before sending the port information message
// (missing dependency at runtime, for example).
console.error(`Error starting "${builderPkg.name}" dev server:`, err);
await this.sendError(
req,
res,
nowRequestId,
'NO_STATUS_CODE_FROM_DEV_SERVER',
502
);
return;
}
if (devServerResult) {
const { port, pid } = devServerResult;
this.devServerPids.add(pid);
res.once('close', () => {
debug(`Killing builder dev server with PID ${pid}`);
treeKill(pid)
.then(() => {
treeKill(pid).then(
() => {
this.devServerPids.delete(pid);
debug(`Killed builder dev server with PID ${pid}`);
})
.catch((err: Error) => {
},
(err: Error) => {
this.devServerPids.delete(pid);
debug(
`Failed to kill builder dev server with PID ${pid}: ${err}`
);
});
}
);
});
debug(
`Proxying to "${builderPkg.name}" dev server (port=${port}, pid=${pid})`
);
// Mix in the routing based query parameters
const parsed = url.parse(req.url || '/', true);
Object.assign(parsed.query, uri_args);
req.url = url.format({
pathname: parsed.pathname,
query: parsed.query,
});
this.setResponseHeaders(res, nowRequestId);
return proxyPass(
req,
res,
@@ -1482,7 +1532,7 @@ export default class DevServer {
false
);
} else {
debug(`Skipping \`startDevServer()\` for ${match.src}`);
debug(`Skipping \`startDevServer()\` for ${match.entrypoint}`);
}
}

View File

@@ -340,9 +340,15 @@ export default class Now extends EventEmitter {
return new Error(error.message);
}
async listSecrets() {
const { secrets } = await this.retry(async bail => {
const res = await this._fetch('/now/secrets');
async listSecrets(next) {
const payload = await this.retry(async bail => {
let secretsUrl = '/v3/now/secrets?limit=20';
if (next) {
secretsUrl += `&until=${next}`;
}
const res = await this._fetch(secretsUrl);
if (res.status === 200) {
// What we want
@@ -356,7 +362,7 @@ export default class Now extends EventEmitter {
throw await responseError(res, 'Failed to list secrets');
});
return secrets;
return payload;
}
async list(app, { version = 4, meta = {}, nextTimestamp } = {}) {

View File

@@ -1,8 +1,43 @@
import Now from '.';
export default class Secrets extends Now {
ls() {
return this.listSecrets();
ls(next) {
return this.listSecrets(next);
}
getSecretByNameOrId(nameOrId) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /secrets/${nameOrId}`);
}
const res = await this._fetch(`/now/secrets/${nameOrId}`, {
method: 'GET',
});
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /secrets/${nameOrId}`);
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
}
if (res.status === 404) {
return bail(new Error('Not Found'));
}
if (res.status === 400) {
return bail(new Error('Bad Request'));
}
const body = await res.json();
if (res.status !== 200) {
throw new Error(body.error.message);
}
return body;
});
}
rm(nameOrId) {

View File

@@ -1,3 +1,3 @@
export default (_req, res) => {
module.exports = (_req, res) => {
res.end('current date: ' + new Date().toISOString());
};

View File

@@ -1,3 +1,3 @@
export default (_req, res) => {
module.exports = (_req, res) => {
res.end('random number: ' + Math.random());
};

View File

@@ -2373,7 +2373,7 @@ test('now secret ls', async t => {
console.log(output.exitCode);
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stdout, /secrets? found under/gm, formatOutput(output));
t.regex(output.stdout, /Secrets found under/gm, formatOutput(output));
t.regex(output.stdout, new RegExp(), formatOutput(output));
});

View File

@@ -2098,7 +2098,7 @@ test('now secret ls', async t => {
console.log(output.exitCode);
t.is(output.exitCode, 0, formatOutput(output));
t.regex(output.stdout, /secrets? found under/gm, formatOutput(output));
t.regex(output.stdout, /Secrets found under/gm, formatOutput(output));
t.regex(output.stdout, new RegExp(), formatOutput(output));
});

View File

@@ -1,6 +1,6 @@
{
"name": "@now/node",
"version": "1.5.2-canary.0",
"version": "1.5.2-canary.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/node-js",

View File

@@ -401,6 +401,14 @@ export async function prepareCache({
return cache;
}
interface PortInfo {
port: number;
}
function isPortInfo(v: any): v is PortInfo {
return v && typeof v.port === 'number';
}
export async function startDevServer({
entrypoint,
workPath,
@@ -420,6 +428,18 @@ export async function startDevServer({
},
});
const { pid } = child;
const { port } = await once<{ port: number }>(child, 'message');
return { port, pid };
const onMessage = once<{ port: number }>(child, 'message');
const onExit = once<{ code: number; signal: string | null }>(child, 'exit');
const result = await Promise.race([onMessage, onExit]);
onExit.cancel();
onMessage.cancel();
if (isPortInfo(result)) {
// "message" event
return { port: result.port, pid };
} else {
// "exit" event
throw new Error(
`Failed to start dev server for "${entrypoint}" (code=${result.code}, signal=${result.signal})`
);
}
}