xit('works', done => {
    const workflow = new NodeWorkflow(new NodeJsSyncHost(), { dryRun: true });
    const collection = path.join(__dirname, '../../../../schematics/angular/package.json');

    workflow.execute({
      collection,
      schematic: 'ng-new',
      options: { name: 'workflow-test', version: '6.0.0-rc.4' },
    }).toPromise().then(done, done.fail);
  });
Example #2
0
export async function main({
  args,
  stdout = process.stdout,
  stderr = process.stderr,
}: MainOptions): Promise<0 | 1> {

  const argv = parseArgs(args);

  /** Create the DevKit Logger used through the CLI. */
  const logger = createConsoleLogger(argv['verbose'], stdout, stderr);
  if (argv.help) {
    logger.info(getUsage());

    return 0;
  }

  /** Get the collection an schematic name from the first argument. */
  const {
    collection: collectionName,
    schematic: schematicName,
  } = parseSchematicName(argv._.shift() || null);
  const isLocalCollection = collectionName.startsWith('.') || collectionName.startsWith('/');

  /** If the user wants to list schematics, we simply show all the schematic names. */
  if (argv['list-schematics']) {
    try {
      const engineHost = new NodeModulesEngineHost();
      const engine = new SchematicEngine(engineHost);
      const collection = engine.createCollection(collectionName);
      logger.info(engine.listSchematicNames(collection).join('\n'));
    } catch (error) {
      logger.fatal(error.message);

      return 1;
    }

    return 0;
  }

  if (!schematicName) {
    logger.info(getUsage());

    return 1;
  }

  /** Gather the arguments for later use. */
  const debug: boolean = argv.debug === null ? isLocalCollection : argv.debug;
  const dryRun: boolean = argv['dry-run'] === null ? debug : argv['dry-run'];
  const force = argv['force'];
  const allowPrivate = argv['allow-private'];

  /** Create a Virtual FS Host scoped to where the process is being run. **/
  const fsHost = new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(process.cwd()));

  /** Create the workflow that will be executed with this run. */
  const workflow = new NodeWorkflow(fsHost, { force, dryRun });

  // Indicate to the user when nothing has been done. This is automatically set to off when there's
  // a new DryRunEvent.
  let nothingDone = true;

  // Logging queue that receives all the messages to show the users. This only get shown when no
  // errors happened.
  let loggingQueue: string[] = [];
  let error = false;

  /**
   * Logs out dry run events.
   *
   * All events will always be executed here, in order of discovery. That means that an error would
   * be shown along other events when it happens. Since errors in workflows will stop the Observable
   * from completing successfully, we record any events other than errors, then on completion we
   * show them.
   *
   * This is a simple way to only show errors when an error occur.
   */
  workflow.reporter.subscribe((event: DryRunEvent) => {
    nothingDone = false;

    switch (event.kind) {
      case 'error':
        error = true;

        const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist';
        logger.warn(`ERROR! ${event.path} ${desc}.`);
        break;
      case 'update':
        loggingQueue.push(tags.oneLine`
        ${terminal.white('UPDATE')} ${event.path} (${event.content.length} bytes)
      `);
        break;
      case 'create':
        loggingQueue.push(tags.oneLine`
        ${terminal.green('CREATE')} ${event.path} (${event.content.length} bytes)
      `);
        break;
      case 'delete':
        loggingQueue.push(`${terminal.yellow('DELETE')} ${event.path}`);
        break;
      case 'rename':
        loggingQueue.push(`${terminal.blue('RENAME')} ${event.path} => ${event.to}`);
        break;
    }
  });


  /**
   * Listen to lifecycle events of the workflow to flush the logs between each phases.
   */
  workflow.lifeCycle.subscribe(event => {
    if (event.kind == 'workflow-end' || event.kind == 'post-tasks-start') {
      if (!error) {
        // Flush the log queue and clean the error state.
        loggingQueue.forEach(log => logger.info(log));
      }

      loggingQueue = [];
      error = false;
    }
  });


  /**
   * Remove every options from argv that we support in schematics itself.
   */
  const parsedArgs = Object.assign({}, argv);
  delete parsedArgs['--'];
  for (const key of booleanArgs) {
    delete parsedArgs[key];
  }

  /**
   * Add options from `--` to args.
   */
  const argv2 = minimist(argv['--']);
  for (const key of Object.keys(argv2)) {
    parsedArgs[key] = argv2[key];
  }

  // Pass the rest of the arguments as the smart default "argv". Then delete it.
  workflow.registry.addSmartDefaultProvider('argv', (schema: JsonObject) => {
    if ('index' in schema) {
      return argv._[Number(schema['index'])];
    } else {
      return argv._;
    }
  });
  delete parsedArgs._;


  /**
   *  Execute the workflow, which will report the dry run events, run the tasks, and complete
   *  after all is done.
   *
   *  The Observable returned will properly cancel the workflow if unsubscribed, error out if ANY
   *  step of the workflow failed (sink or task), with details included, and will only complete
   *  when everything is done.
   */
  try {
    await workflow.execute({
      collection: collectionName,
      schematic: schematicName,
      options: parsedArgs,
      allowPrivate: allowPrivate,
      debug: debug,
      logger: logger,
    })
      .toPromise();

    if (nothingDone) {
      logger.info('Nothing to be done.');
    }

    return 0;

  } catch (err) {
    if (err instanceof UnsuccessfulWorkflowExecution) {
      // "See above" because we already printed the error.
      logger.fatal('The Schematic workflow failed. See above.');
    } else if (debug) {
      logger.fatal('An error occured:\n' + err.stack);
    } else {
      logger.fatal(err.stack || err.message);
    }

    return 1;
  }
}
Example #3
0
  process.exit(0);
  throw 0;  // TypeScript doesn't know that process.exit() never returns.
}


/** Gather the arguments for later use. */
const debug: boolean = argv.debug === null ? isLocalCollection : argv.debug;
const dryRun: boolean = argv['dry-run'] === null ? debug : argv['dry-run'];
const force = argv['force'];
const allowPrivate = argv['allowPrivate'];

/** Create a Virtual FS Host scoped to where the process is being run. **/
const fsHost = new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(process.cwd()));

/** Create the workflow that will be executed with this run. */
const workflow = new NodeWorkflow(fsHost, { force, dryRun });

// Indicate to the user when nothing has been done. This is automatically set to off when there's
// a new DryRunEvent.
let nothingDone = true;

// Logging queue that receives all the messages to show the users. This only get shown when no
// errors happened.
let loggingQueue: string[] = [];
let error = false;

/**
 * Logs out dry run events.
 *
 * All events will always be executed here, in order of discovery. That means that an error would
 * be shown along other events when it happens. Since errors in workflows will stop the Observable