This repository was archived by the owner on Apr 4, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 155
Schematics #1
Merged
Merged
Schematics #1
Changes from 11 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
6cf93ab
refactor: various housekeeping stuff
hansl 6cfd8ca
build: add sdk-admin binary, bootstrap-local, ...
hansl 9f618a1
feat(@angular/schematics): implement the schematics library
hansl 4a53268
feat(@angular/schematics-cli): Implement the first version of the Sch…
hansl b48f616
build: implement the first bazel configuration for DevKit
hansl a46c64e
refactor: add license headers to Schematics
hansl d422f9b
docs(@angular/schematics): add documentation for schematics
hansl 2fe2836
refactor: refactor interfaces
hansl 3f93cdc
fix(@angular/schematics-cli): fix component schematics options
hansl 62043f7
fix(@angular/schematics): fix the null protocol for node-modules-engi…
hansl bd11991
refactor: build multiple engine-host to help with use cases
hansl 09b5cff
refactor: resolve comments from Mike and Ben
hansl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Disable sandboxing because it's too slow. | ||
# https://github.com/bazelbuild/bazel/issues/2424 | ||
# TODO(alexeagle): do use the sandbox on CI | ||
#build --spawn_strategy=standalone | ||
|
||
# Performance: avoid stat'ing input files | ||
build --watchfs | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
6 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package(default_visibility = ["//visibility:public"]) | ||
exports_files(["tsconfig.json"]) | ||
|
||
# NOTE: this will move to node_modules/BUILD in a later release | ||
filegroup(name = "node_modules", srcs = glob(["node_modules/**/*"])) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") | ||
|
||
git_repository( | ||
name = "io_bazel_rules_typescript", | ||
remote = "https://github.com/bazelbuild/rules_typescript.git", | ||
tag = "0.0.2", | ||
) | ||
|
||
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories", "yarn_install") | ||
|
||
node_repositories() | ||
yarn_install(package_json = "//:package.json") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
require('../lib/bootstrap-local'); | ||
const packages = require('../lib/packages').packages; | ||
|
||
require(packages['@angular/schematics-cli'].bin['schematics']); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
|
||
/** | ||
* This file is useful for not having to load bootstrap-local in various javascript. | ||
* Simply use package.json to have npm scripts that use this script as well, or use | ||
* this script directly. | ||
*/ | ||
|
||
require('../lib/bootstrap-local'); | ||
|
||
const minimist = require('minimist'); | ||
|
||
const args = minimist(process.argv.slice(2)); | ||
|
||
|
||
switch (args._[0]) { | ||
case 'test': require('../scripts/test').default(args); break; | ||
|
||
default: console.log('Unknown command: ' + args._[0]); break; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/** | ||
* @license | ||
* Copyright Google Inc. All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
/* eslint-disable no-console */ | ||
'use strict'; | ||
|
||
const fs = require('fs'); | ||
const path = require('path'); | ||
const ts = require('typescript'); | ||
|
||
|
||
Error.stackTraceLimit = Infinity; | ||
|
||
global._SdkIsLocal = true; | ||
global._SdkRoot = path.resolve(__dirname, '..'); | ||
global._SdkPackages = require('./packages').packages; | ||
global._SdkTools = require('./packages').tools; | ||
|
||
global._SdkRequireHook = null; | ||
|
||
|
||
const compilerOptions = ts.readConfigFile(path.join(__dirname, '../tsconfig.json'), p => { | ||
return fs.readFileSync(p, 'utf-8'); | ||
}).config; | ||
|
||
|
||
const oldRequireTs = require.extensions['.ts']; | ||
require.extensions['.ts'] = function (m, filename) { | ||
// If we're in node module, either call the old hook or simply compile the | ||
// file without transpilation. We do not touch node_modules/**. | ||
// We do touch `Angular SDK` files anywhere though. | ||
if (!filename.match(/@angular\/cli\b/) && filename.match(/node_modules/)) { | ||
if (oldRequireTs) { | ||
return oldRequireTs(m, filename); | ||
} | ||
return m._compile(fs.readFileSync(filename), filename); | ||
} | ||
|
||
// Node requires all require hooks to be sync. | ||
const source = fs.readFileSync(filename).toString(); | ||
|
||
try { | ||
let result = ts.transpile(source, compilerOptions['compilerOptions'], filename); | ||
|
||
if (global._SdkRequireHook) { | ||
result = global._SdkRequireHook(result, filename); | ||
} | ||
|
||
// Send it to node to execute. | ||
return m._compile(result, filename); | ||
} catch (err) { | ||
console.error('Error while running script "' + filename + '":'); | ||
console.error(err.stack); | ||
throw err; | ||
} | ||
}; | ||
|
||
|
||
// If we're running locally, meaning npm linked. This is basically "developer mode". | ||
if (!__dirname.match(new RegExp(`\\${path.sep}node_modules\\${path.sep}`))) { | ||
const packages = require('./packages').packages; | ||
|
||
// We mock the module loader so that we can fake our packages when running locally. | ||
const Module = require('module'); | ||
const oldLoad = Module._load; | ||
const oldResolve = Module._resolveFilename; | ||
|
||
Module._resolveFilename = function (request, parent) { | ||
if (request in packages) { | ||
return packages[request].main; | ||
} else if (request.startsWith('@angular/cli/')) { | ||
// We allow deep imports (for now). | ||
// TODO: move tests to inside @angular/cli package so they don't have to deep import. | ||
const dir = path.dirname(parent.filename); | ||
return path.relative(dir, path.join(__dirname, '../packages', request)); | ||
} else { | ||
let match = Object.keys(packages).find(pkgName => request.startsWith(pkgName + '/')); | ||
if (match) { | ||
const p = path.join(packages[match].root, request.substr(match.length)); | ||
return oldResolve.call(this, p, parent); | ||
} else { | ||
return oldResolve.apply(this, arguments); | ||
} | ||
} | ||
}; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* @license | ||
* Copyright Google Inc. All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
'use strict'; | ||
|
||
const fs = require('fs'); | ||
const glob = require('glob'); | ||
const path = require('path'); | ||
|
||
const packageRoot = path.join(__dirname, '../packages'); | ||
const toolsRoot = path.join(__dirname, '../tools'); | ||
const distRoot = path.join(__dirname, '../dist'); | ||
|
||
|
||
// All the supported packages. Go through the packages directory and create a _map of | ||
// name => fullPath. | ||
const packages = | ||
[].concat( | ||
glob.sync(path.join(packageRoot, '*/package.json')), | ||
glob.sync(path.join(packageRoot, '*/*/package.json'))) | ||
.filter(p => !p.match(/blueprints/)) | ||
.map(pkgPath => [pkgPath, path.relative(packageRoot, path.dirname(pkgPath))]) | ||
.map(([pkgPath, pkgName]) => { | ||
return { name: pkgName, root: path.dirname(pkgPath) }; | ||
}) | ||
.reduce((packages, pkg) => { | ||
let pkgJson = JSON.parse(fs.readFileSync(path.join(pkg.root, 'package.json'), 'utf8')); | ||
let name = pkgJson['name']; | ||
let bin = {}; | ||
Object.keys(pkgJson['bin'] || {}).forEach(binName => { | ||
bin[binName] = path.resolve(pkg.root, pkgJson['bin'][binName]); | ||
}); | ||
|
||
packages[name] = { | ||
dist: path.join(distRoot, pkg.name), | ||
packageJson: path.join(pkg.root, 'package.json'), | ||
root: pkg.root, | ||
relative: path.relative(path.dirname(__dirname), pkg.root), | ||
main: path.resolve(pkg.root, 'src/index.ts'), | ||
bin: bin | ||
}; | ||
return packages; | ||
}, {}); | ||
|
||
const tools = glob.sync(path.join(toolsRoot, '**/package.json')) | ||
.map(toolPath => path.relative(toolsRoot, path.dirname(toolPath))) | ||
.map(toolName => { | ||
const root = path.join(toolsRoot, toolName); | ||
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); | ||
const name = pkgJson['name']; | ||
const dist = path.join(distRoot, toolName); | ||
|
||
return { | ||
name, | ||
main: path.join(dist, pkgJson['main']), | ||
mainTs: path.join(toolsRoot, toolName, pkgJson['main'].replace(/\.js$/, '.ts')), | ||
root, | ||
packageJson: path.join(toolsRoot, toolName, 'package.json'), | ||
dist | ||
}; | ||
}) | ||
.reduce((tools, tool) => { | ||
tools[tool.name] = tool; | ||
return tools; | ||
}, {}); | ||
|
||
|
||
module.exports = { packages, tools }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package(default_visibility=["//visibility:private"]) | ||
load("@io_bazel_rules_typescript//:defs.bzl", "ts_library") | ||
|
||
exports_files(["tsconfig.json"]) | ||
|
||
|
||
ts_library( | ||
name = "schematics", | ||
srcs = [ | ||
"src/index.ts", | ||
], | ||
deps = [ | ||
"//packages/schematics/src/engine", | ||
"//packages/schematics/src/exception", | ||
"//packages/schematics/src/rules", | ||
"//packages/schematics/src/sink", | ||
"//packages/schematics/src/tree", | ||
"//packages/schematics/tooling", | ||
], | ||
tsconfig = "//:tsconfig.json", | ||
visibility = [ "//visibility:public" ], | ||
module_name = "@angular/schematics", | ||
module_root = "src" | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Schematics | ||
> A scaffolding library for the modern web. | ||
|
||
## Description | ||
Schematics are generators that transform an existing filesystem. It can create files, refactor existing files, or move files around. | ||
|
||
What distinguish Schematics from other generators, such as Yeoman or Yarn Create, is that schematics are purely descriptive; no changes are applied to the actual filesystem until everything is ready to be committed. There is no side effect, by design, in Schematics. | ||
|
||
# Glossary | ||
|
||
| Term | Description | | ||
|------|-------------| | ||
| **Schematics** | A generator that execute descriptive code without side effects on an existing file system. | | ||
| **Collection** | A list of schematics metadata. Schematics can be referred by name inside a collection. | | ||
| **Tool** | The code using the Schematics library. | | ||
| **Tree** | A staging area for changes, containing the original file system, and a list of changes to apply to it. | | ||
| **Rule** | A function that applies actions to a `Tree`. It returns a new Tree that will contain all transformations to be applied. | | ||
| **Source** | A function that creates an entirely new `Tree` from an empty filesystem. For example, a file source could read files from disk and create a Create Action for each of those. | ||
| **Action** | A atomic operation to be validated and committed to a filesystem or a `Tree`. Actions are created by schematics. | | ||
| **Sink** | The final destination of all `Action`s. | | ||
|
||
# Tooling | ||
Schematics is a library, and does not work by itself. A reference CLI is available in [`@angular/schematics-cli`](../schematics_cli/README.md). This document explain the library usage and the tooling API, but does not go into the tool implementation itself. | ||
|
||
The tooling is responsible for the following tasks: | ||
|
||
1. Create the Schematic Engine, and pass in a Collection and Schematic loader. | ||
1. Understand and respect the Schematics metadata and dependencies between collections. Schematics can refer to dependencies, and it's the responsibility of the tool to honor those dependencies. The reference CLI uses NPM packages for its collections. | ||
1. Create the Options object. Options can be anything, but the schematics can specify a JSON Schema that should be respected. The reference CLI, for example, parse the arguments as a JSON object and validate it with the Schema specified by the collection. | ||
1. Call the schematics with the original Tree. The tree should represent the initial state of the filesystem. The reference CLI uses the current directory for this. | ||
1. Create a Sink and commit the result of the schematics to the Sink. Many sinks are provided by the library; FileSystemSink and DryRunSink are examples. | ||
1. Output any logs propagated by the library, including debugging information. | ||
|
||
The tooling API is composed of the following pieces: | ||
|
||
## Engine | ||
The `SchematicEngine` is responsible for loading and constructing `Collection`s and `Schematics`'. When creating an engine, the tooling provides an `EngineHost` interface that understands how to create a `CollectionDescription` from a name, and how to create a `Schematic | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unclosed backtick at `Schematic |
||
|
||
# Examples | ||
|
||
## Simple | ||
An example of a simple Schematics which creates a "hello world" file, using an option to determine its path: | ||
|
||
```typescript | ||
import {Tree} from '@angular/schematics'; | ||
|
||
export default function MySchematic(options: any) { | ||
return (tree: Tree) => { | ||
tree.create(options.path + '/hi', 'Hello world!'); | ||
return tree; | ||
}; | ||
} | ||
``` | ||
|
||
A few things from this example: | ||
|
||
1. The function receives the list of options from the tooling. | ||
1. It returns a [`Rule`](src/engine/interface.ts#L73), which is a transformation from a `Tree` to another `Tree`. | ||
|
||
|
||
|
||
# Future Work | ||
|
||
Schematics is not done yet. Here's a list of things we are considering: | ||
|
||
* Smart defaults for Options. Having a JavaScript function for default values based on other default values. | ||
* Prompt for input options. This should only be prompted for the original schematics, dependencies to other schematics should not trigger another prompting. | ||
* Tasks for running tooling-specific jobs before and after a schematics has been scaffolded. Such tasks can involve initialize git, or npm install. A specific list of tasks should be provided by the tool, with unsupported tasks generating an error. | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parse
->parses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.