UNPKG

20.2 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const sdk_client_1 = require("@haiku/sdk-client");
4const sdk_inkstone_1 = require("@haiku/sdk-inkstone");
5const errors_1 = require("@haiku/sdk-inkstone/lib/errors");
6const ProjectDefinitions_1 = require("@haiku/sdk-client/lib/ProjectDefinitions");
7const bootstrapSceneFilesSync_1 = require("@haiku/sdk-client/lib/bootstrapSceneFilesSync");
8const createProjectFiles_1 = require("@haiku/sdk-client/lib/createProjectFiles");
9const chalk = require("chalk");
10const child_process_1 = require("child_process");
11const dedent = require("dedent");
12const fs = require("fs");
13// @ts-ignore
14const hasbin = require("hasbin");
15const inquirer = require("inquirer");
16const _ = require("lodash");
17const path = require("path");
18// @ts-ignore
19const prependFile = require("prepend-file");
20const nib_1 = require("./nib");
21// tslint:disable-next-line:no-var-requires
22const pkg = require('./../package.json');
23const cli = new nib_1.Nib({
24 name: 'haiku',
25 version: pkg.version,
26 description: 'The Haiku CLI — developer utilities for automating Haiku actions and performing local and' +
27 ' server-enabled actions without requiring the desktop app.',
28 preAction(context) {
29 sdk_client_1.client.config.getenv();
30 },
31 commands: [
32 {
33 name: 'list',
34 action: doList,
35 flags: [
36 {
37 name: 'organizations',
38 defaultValue: undefined,
39 description: 'include to list organizations your account is a member of instead of projects',
40 },
41 ],
42 description: 'Lists your team projects',
43 },
44 {
45 name: 'change-password',
46 action: doChangePassword,
47 description: 'Changes your Haiku account password (interactive)',
48 },
49 {
50 name: 'clone',
51 action: doClone,
52 description: 'Clone a Haiku project to your filesystem, passing through to git clone',
53 args: [
54 {
55 name: 'project-name',
56 required: true,
57 usage: 'Clone a Haiku project to your filesystem, passing through to git clone',
58 },
59 {
60 name: 'destination',
61 required: false,
62 usage: 'Optional: location on the file system where the project should be cloned',
63 },
64 ],
65 },
66 {
67 name: 'delete',
68 action: doDelete,
69 description: 'Deletes a Haiku project for your entire team. Cannot be undone.',
70 args: [
71 {
72 name: 'project-name',
73 required: false,
74 usage: 'Specifies the name of the project to delete (case-sensitive.) If this isn\'t provided, the action' +
75 ' will be interactive.',
76 },
77 ],
78 },
79 {
80 name: 'init',
81 action: doInit,
82 description: 'Inits a project for installing @haiku modules. ' +
83 'Will write or append to a .npmrc in this directory.',
84 },
85 {
86 name: 'install',
87 action: doInstall,
88 description: 'Install a Haiku project as an npm module, requires a package.json',
89 args: [
90 {
91 name: 'project-name',
92 required: true,
93 usage: 'Specifies the name of the project to install as a dependency. Case-sensitive.',
94 },
95 ],
96 },
97 {
98 name: 'login',
99 action: doLogin,
100 description: 'Logs into Haiku services. (interactive)',
101 },
102 {
103 name: 'logout',
104 action: doLogout,
105 description: 'Logs out of Haiku services.',
106 },
107 {
108 name: 'update',
109 aliases: ['upgrade'],
110 args: [
111 {
112 name: 'project-name',
113 required: false,
114 usage: 'Specifies the name of the project to update as a dependency. Case-sensitive. If not provided,' +
115 ' will update all detected Haiku projects.',
116 },
117 {
118 name: 'version',
119 required: false,
120 usage: 'Specifies the version to update specified dependency to. If not provided, will update to the' +
121 ' latest available version.',
122 },
123 ],
124 action: doUpdate,
125 description: 'Updates dependencies',
126 },
127 {
128 name: 'generate',
129 aliases: ['g'],
130 args: [
131 {
132 name: 'component-name',
133 required: true,
134 usage: 'Specifies the name of new component to be generated. Case-sensitive and must be unique.',
135 },
136 ],
137 action: generateComponent,
138 description: 'Generate new component',
139 },
140 ],
141});
142exports.cli = cli;
143function ensureAuth(context, cb) {
144 const authToken = sdk_client_1.client.config.getAuthToken();
145 if (authToken) {
146 sdk_inkstone_1.inkstone.setConfig({ authToken });
147 cb(authToken);
148 return;
149 }
150 context.writeLine('You must be authenticated to do that.');
151 doLogin(context, () => {
152 const newToken = sdk_client_1.client.config.getAuthToken();
153 if (newToken) {
154 sdk_inkstone_1.inkstone.setConfig({ authToken: newToken });
155 cb(newToken);
156 return;
157 }
158 context.writeLine('Hm, that didn\'t work. Let\'s try again.');
159 ensureAuth(context, cb);
160 });
161}
162function doChangePassword(context) {
163 ensureAuth(context, (token) => {
164 inquirer.prompt([
165 {
166 type: 'password',
167 name: 'OldPassword',
168 message: 'Old Password:',
169 },
170 {
171 type: 'password',
172 name: 'NewPassword',
173 message: 'New Password:',
174 },
175 {
176 type: 'password',
177 name: 'NewPassword2',
178 message: 'New Password (confirm):',
179 },
180 ]).then((answers) => {
181 if (answers.NewPassword !== answers.NewPassword2) {
182 context.writeLine(chalk.red('New passwords do not match.'));
183 process.exit(1);
184 }
185 const params = {
186 OldPassword: answers.OldPassword,
187 NewPassword: answers.NewPassword,
188 };
189 sdk_inkstone_1.inkstone.user.changePassword(token, params, (err, responseBody, response) => {
190 if (err) {
191 context.writeLine(chalk.bold(`Unabled to change password: `) + err);
192 process.exit(1);
193 }
194 else {
195 context.writeLine(chalk.green('Password updated.'));
196 }
197 });
198 });
199 });
200}
201function doClone(context) {
202 const projectName = context.args['project-name'];
203 let destination = context.args.destination || projectName;
204 if (destination.charAt(destination.length - 1) !== '/') {
205 destination += '/';
206 }
207 ensureAuth(context, (token) => {
208 context.writeLine('Cloning project...');
209 sdk_inkstone_1.inkstone.project.get({ Name: projectName }, (getByNameErr, projectAndCredentials) => {
210 if (getByNameErr) {
211 switch (getByNameErr.message) {
212 case errors_1.ErrorCode.ErrorCodeProjectNotFound:
213 context.writeLine(chalk.bold(`Project ${projectName} not found.`));
214 break;
215 case errors_1.ErrorCode.ErrorCodeProjectNameRequired:
216 context.writeLine(chalk.bold(`Project name is required.`));
217 break;
218 }
219 process.exit(1);
220 }
221 sdk_client_1.client.git.cloneRepo(projectAndCredentials.RepositoryUrl, destination, (cloneErr) => {
222 if (cloneErr) {
223 context.writeLine(chalk.red('Error cloning project. Use the --verbose flag for more information.'));
224 process.exit(1);
225 }
226 else {
227 context.writeLine(`Project ${chalk.bold(projectName)} cloned to ${chalk.bold(destination)}`);
228 process.exit(0);
229 }
230 });
231 });
232 });
233}
234function doDelete(context) {
235 ensureAuth(context, (token) => {
236 context.writeLine(chalk.bold('Please note that deleting this project will delete it for your entire team.'));
237 context.writeLine(chalk.red('Deleting a project cannot be undone!'));
238 const actuallyDelete = (finalProjectName) => {
239 sdk_inkstone_1.inkstone.project.deleteByName({ Name: finalProjectName }, (err) => {
240 if (err) {
241 context.writeLine(chalk.red('Error deleting project. Does this project exist?'));
242 process.exit(1);
243 }
244 else {
245 context.writeLine(chalk.green('Project deleted!'));
246 process.exit(0);
247 }
248 });
249 };
250 let projectName = context.args['project-name'];
251 if (projectName) {
252 actuallyDelete(projectName);
253 }
254 else {
255 inquirer
256 .prompt([
257 {
258 type: 'input',
259 name: 'name',
260 message: 'Project Name:',
261 },
262 ])
263 .then((answers) => {
264 projectName = answers.name;
265 context.writeLine('Deleting project...');
266 actuallyDelete(projectName);
267 });
268 }
269 });
270}
271function doInit(context) {
272 // Set up @haiku scope for this project if it doesn't exist
273 let npmrc = '';
274 try {
275 npmrc = fs.readFileSync('.npmrc').toString();
276 }
277 catch (exception) {
278 if (exception.code === 'ENOENT') {
279 // file not found, this is fine
280 }
281 else {
282 // different error, should throw
283 throw (exception);
284 }
285 }
286 if (npmrc.indexOf('@haiku') === -1) {
287 prependFile.sync('.npmrc', dedent `
288 //reservoir.haiku.ai:8910/:_authToken=
289 @haiku:registry=https://reservoir.haiku.ai:8910/\n
290 `);
291 }
292}
293function doInstall(context) {
294 const projectName = context.args['project-name'];
295 ensureAuth(context, () => {
296 // ensure that npm is installed
297 hasbin('npm', (result) => {
298 if (result) {
299 // ensure that there's a package.json in this directory
300 if (fs.existsSync(process.cwd() + '/package.json')) {
301 context.writeLine('Installing ' + projectName + '...');
302 const packageJson = sdk_client_1.client.npm.readPackageJson();
303 if (!packageJson.dependencies) {
304 packageJson.dependencies = {};
305 }
306 // construct project string: @haiku/org-project#latest
307 let projectString = '@haiku/';
308 sdk_inkstone_1.inkstone.organization.list((listErr, orgs) => {
309 if (listErr) {
310 context.writeLine(chalk.red('There was an error retrieving your account information.') +
311 ' Please ensure that you have internet access.' +
312 ' If this problem persists, please contact support@haiku.ai and tell us that you don\'t have an' +
313 ' organization associated with your account.');
314 process.exit(1);
315 }
316 // TODO: for multi-org support, get the org name more intelligently than this
317 projectString += orgs[0].Name.toLowerCase() + '-';
318 sdk_inkstone_1.inkstone.project.get({ Name: projectName }, (getByNameErr, projectAndCredentials) => {
319 if (getByNameErr) {
320 context.writeLine(chalk.red('That project wasn\'t found.') +
321 ' Note that project names are CaseSensitive. ' +
322 'Please ensure that you have the correct project name, that you\'re logged into the correct' +
323 ' account, and that you have internet access.');
324 process.exit(1);
325 }
326 projectString += projectAndCredentials.Name.toLowerCase();
327 // now projectString should be @haiku/org-project
328 packageJson.dependencies[projectString] = 'latest';
329 // Set up @haiku scope for this project if it doesn't exist
330 doInit(context);
331 sdk_client_1.client.npm.writePackageJson(packageJson);
332 try {
333 child_process_1.execSync('npm install');
334 }
335 catch (e) {
336 context.writeLine(`${chalk.red('npm install failed.')} Your Haiku packages have been injected` +
337 ' into package.json, but npm install failed. Please try again.');
338 process.exit(1);
339 }
340 context.writeLine(chalk.green('Haiku project installed successfully.'));
341 process.exit(0);
342 });
343 });
344 }
345 else {
346 context.writeLine(chalk.red('haiku install can only be used at the root of a project with a package.json.'));
347 context.writeLine('You can use ' + chalk.bold('haiku clone ProjectName [/Optional/Destination]') +
348 ' to clone the project\'s git repo directly.');
349 process.exit(1);
350 }
351 }
352 else {
353 context.writeLine(chalk.red('npm was not found on this machine. ') +
354 ' We recommend installing it with nvm: https://github.com/creationix/nvm');
355 process.exit(1);
356 }
357 });
358 });
359}
360function doList(context) {
361 ensureAuth(context, () => {
362 if (context.flags.organizations) {
363 sdk_inkstone_1.inkstone.organization.list((err, organizations, resp) => {
364 if (organizations === undefined || organizations.length === 0) {
365 context.writeLine('You are not a member of any organizations.');
366 }
367 else {
368 context.writeLine(chalk.cyan('Your Organizations:'));
369 _.forEach(organizations, (org) => {
370 context.writeLine(' ' + org.Name);
371 });
372 }
373 process.exit(0);
374 });
375 }
376 else {
377 sdk_inkstone_1.inkstone.project.list((err, projects) => {
378 if (!projects || projects.length === 0) {
379 context.writeLine('No existing projects. Use ' + chalk.bold('haiku generate') + ' to make a new one!');
380 process.exit(0);
381 }
382 else {
383 context.writeLine(chalk.cyan('Your team\'s Haiku projects:'));
384 context.writeLine('(To work with one, call ' + chalk.bold('haiku clone project_name') + ' or ' +
385 chalk.bold('haiku install project_name'));
386 _.forEach(projects, (project) => {
387 context.writeLine(' ' + project.Name);
388 });
389 process.exit(0);
390 }
391 });
392 }
393 });
394}
395function doLogin(context, cb) {
396 context.writeLine('Enter your Haiku credentials.');
397 let username = '';
398 let password = '';
399 inquirer.prompt([
400 {
401 type: 'input',
402 name: 'username',
403 message: 'Email:',
404 },
405 {
406 type: 'password',
407 name: 'password',
408 message: 'Password:',
409 },
410 ]).then((answers) => {
411 username = answers.username;
412 password = answers.password;
413 sdk_inkstone_1.inkstone.user.authenticate(username, password, (err, authResponse, httpResponse) => {
414 if (err !== undefined) {
415 if (httpResponse && httpResponse.statusCode === 403) {
416 context.writeLine(chalk.bold.yellow('You must verify your email address before logging in.'));
417 }
418 else {
419 context.writeLine(chalk.bold.red('Username or password incorrect.'));
420 }
421 if (context.flags.verbose) {
422 context.writeLine(err.toString());
423 }
424 }
425 else {
426 sdk_client_1.client.config.setAuthToken(authResponse.Token);
427 context.writeLine(chalk.bold.green(`Welcome ${username}!`));
428 }
429 if (cb) {
430 cb();
431 }
432 else {
433 process.exit(0);
434 }
435 });
436 });
437}
438function doLogout() {
439 // TODO: expire auth token on inkstone?
440 sdk_client_1.client.config.setAuthToken('');
441 process.exit(0);
442}
443// TODO: update only @haiku packages, instead of all updatable packages in package.json
444function doUpdate(context) {
445 hasbin('npm', (result) => {
446 if (result) {
447 try {
448 context.writeLine('Updating packages...');
449 child_process_1.execSync('npm update');
450 context.writeLine(chalk.green('Haiku packages updated successfully.'));
451 process.exit(0);
452 }
453 catch (e) {
454 context.writeLine(chalk.red('npm update failed.') +
455 ' This may be a configuration issue with npm. Try running npm install and then running haiku update again.');
456 process.exit(1);
457 }
458 }
459 else {
460 context.writeLine(chalk.red('npm was not found on this machine. ') +
461 ' We recommend installing it with nvm: https://github.com/creationix/nvm');
462 process.exit(1);
463 }
464 });
465}
466function generateComponent(context) {
467 const componentName = context.args['component-name'];
468 context.writeLine('Creating component...');
469 const projectPath = path.join(process.cwd(), componentName);
470 const projectName = componentName;
471 const authorName = null;
472 const organizationName = null;
473 ProjectDefinitions_1.storeConfigValues(projectPath, {
474 username: authorName,
475 branch: ProjectDefinitions_1.DEFAULT_BRANCH_NAME,
476 version: ProjectDefinitions_1.getHaikuComponentInitialVersion(),
477 organization: organizationName,
478 project: projectName,
479 });
480 const projectOptions = {
481 organizationName,
482 projectName,
483 projectPath,
484 authorName,
485 skipContentCreation: false,
486 };
487 createProjectFiles_1.createProjectFiles(projectOptions, () => {
488 context.writeLine('Created initial project files');
489 ProjectDefinitions_1.fetchProjectConfigInfo(projectPath, (err, userconfig) => {
490 if (err) {
491 throw err;
492 }
493 bootstrapSceneFilesSync_1.bootstrapSceneFilesSync(projectPath, 'main', userconfig);
494 context.writeLine('Created main component');
495 });
496 });
497 context.writeLine('Project created');
498 process.exit(0);
499}
500// see ./unimplemented.txt for incomplete player upgrade logic
501//# sourceMappingURL=haiku-cli.js.map
\No newline at end of file