Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/angular/pwa/pwa/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
template,
url,
} from '@angular-devkit/schematics';
import { getWorkspace, updateWorkspace } from '@schematics/angular/utility/workspace';
import { readWorkspace, writeWorkspace } from '@schematics/angular/utility';
import { posix } from 'path';
import { Readable, Writable } from 'stream';
import { Schema as PwaOptions } from './schema';
Expand Down Expand Up @@ -87,7 +87,7 @@ export default function (options: PwaOptions): Rule {
options.title = options.project;
}

const workspace = await getWorkspace(host);
const workspace = await readWorkspace(host);

if (!options.project) {
throw new SchematicsException('Option "project" is required.');
Expand Down Expand Up @@ -158,8 +158,9 @@ export default function (options: PwaOptions): Rule {
// Setup service worker schematic options
const { title, ...swOptions } = options;

await writeWorkspace(host, workspace);

return chain([
updateWorkspace(workspace),
externalSchematic('@schematics/angular', 'service-worker', swOptions),
mergeWith(apply(url('./files/root'), [template({ ...options }), move(sourcePath)])),
mergeWith(
Expand Down
7 changes: 4 additions & 3 deletions packages/schematics/angular/e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import {
strings,
url,
} from '@angular-devkit/schematics';
import { readWorkspace, writeWorkspace } from '../utility';
import { NodeDependencyType, addPackageJsonDependency } from '../utility/dependencies';
import { JSONFile } from '../utility/json-file';
import { latestVersions } from '../utility/latest-versions';
import { relativePathToWorkspaceRoot } from '../utility/paths';
import { getWorkspace, updateWorkspace } from '../utility/workspace';
import { Builders } from '../utility/workspace-models';
import { Schema as E2eOptions } from './schema';

Expand All @@ -41,7 +41,7 @@ function addScriptsToPackageJson(): Rule {
export default function (options: E2eOptions): Rule {
return async (host: Tree) => {
const appProject = options.relatedAppName;
const workspace = await getWorkspace(host);
const workspace = await readWorkspace(host);
const project = workspace.projects.get(appProject);
if (!project) {
throw new SchematicsException(`Project name "${appProject}" doesn't not exist.`);
Expand All @@ -66,8 +66,9 @@ export default function (options: E2eOptions): Rule {
},
});

await writeWorkspace(host, workspace);

return chain([
updateWorkspace(workspace),
mergeWith(
apply(url('./files'), [
applyTemplates({
Expand Down
7 changes: 7 additions & 0 deletions packages/schematics/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
"code generation",
"schematics"
],
"exports": {
"./package.json": "./package.json",
"./utility": "./utility/index.js",
"./utility/*": "./utility/*.js",
"./migrations/migration-collection.json": "./migrations/migration-collection.json",
"./*": "./*.js"
},
"schematics": "./collection.json",
"dependencies": {
"@angular-devkit/core": "0.0.0-PLACEHOLDER",
Expand Down
7 changes: 4 additions & 3 deletions packages/schematics/angular/service-worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript';
import { readWorkspace, writeWorkspace } from '../utility';
import {
addSymbolToNgModuleMetadata,
getEnvironmentExportName,
Expand All @@ -32,7 +33,6 @@ import { addPackageJsonDependency, getPackageJsonDependency } from '../utility/d
import { getAppModulePath } from '../utility/ng-ast-utils';
import { relativePathToWorkspaceRoot } from '../utility/paths';
import { targetBuildNotFoundError } from '../utility/project-targets';
import { getWorkspace, updateWorkspace } from '../utility/workspace';
import { BrowserBuilderOptions } from '../utility/workspace-models';
import { Schema as ServiceWorkerOptions } from './schema';

Expand Down Expand Up @@ -130,7 +130,7 @@ function getTsSourceFile(host: Tree, path: string): ts.SourceFile {

export default function (options: ServiceWorkerOptions): Rule {
return async (host: Tree, context: SchematicContext) => {
const workspace = await getWorkspace(host);
const workspace = await readWorkspace(host);
const project = workspace.projects.get(options.project);
if (!project) {
throw new SchematicsException(`Invalid project name (${options.project})`);
Expand Down Expand Up @@ -163,9 +163,10 @@ export default function (options: ServiceWorkerOptions): Rule {

context.addTask(new NodePackageInstallTask());

await writeWorkspace(host, workspace);

return chain([
mergeWith(templateSource),
updateWorkspace(workspace),
addDependencies(),
updateAppModule(buildOptions.main),
]);
Expand Down
21 changes: 21 additions & 0 deletions packages/schematics/angular/utility/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
Comment thread
dgp1130 marked this conversation as resolved.
* @license
* Copyright Google LLC 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
*/

// Workspace related rules and types
export {
ProjectDefinition,
TargetDefinition,
WorkspaceDefinition,
getWorkspace as readWorkspace,
updateWorkspace,
writeWorkspace,
} from './workspace';
export { Builders as AngularBuilder } from './workspace-models';

// Package dependency related rules and types
export { DependencyType, addDependency } from './dependency';
6 changes: 6 additions & 0 deletions packages/schematics/angular/utility/workspace-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export enum ProjectType {
Library = 'library',
}

/**
* An enum of the official Angular builders.
* Each enum value provides the fully qualified name of the associated builder.
* This enum can be used when analyzing the `builder` fields of project configurations from the
* `angular.json` workspace file.
*/
export enum Builders {
AppShell = '@angular-devkit/build-angular:app-shell',
Server = '@angular-devkit/build-angular:server',
Expand Down
121 changes: 85 additions & 36 deletions packages/schematics/angular/utility/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,109 @@ import { json, workspaces } from '@angular-devkit/core';
import { Rule, Tree, noop } from '@angular-devkit/schematics';
import { ProjectType } from './workspace-models';

function createHost(tree: Tree): workspaces.WorkspaceHost {
return {
async readFile(path: string): Promise<string> {
return tree.readText(path);
},
async writeFile(path: string, data: string): Promise<void> {
return tree.overwrite(path, data);
},
async isDirectory(path: string): Promise<boolean> {
// approximate a directory check
return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;
},
async isFile(path: string): Promise<boolean> {
return tree.exists(path);
},
};
const DEFAULT_WORKSPACE_PATH = '/angular.json';

// re-export the workspace definition types for convenience
export type WorkspaceDefinition = workspaces.WorkspaceDefinition;
export type ProjectDefinition = workspaces.ProjectDefinition;
export type TargetDefinition = workspaces.TargetDefinition;

/**
* A {@link workspaces.WorkspaceHost} backed by a Schematics {@link Tree} instance.
*/
class TreeWorkspaceHost implements workspaces.WorkspaceHost {
Comment thread
dgp1130 marked this conversation as resolved.
constructor(private readonly tree: Tree) {}

async readFile(path: string): Promise<string> {
Comment thread
dgp1130 marked this conversation as resolved.
return this.tree.readText(path);
}

async writeFile(path: string, data: string): Promise<void> {
if (this.tree.exists(path)) {
this.tree.overwrite(path, data);
} else {
this.tree.create(path, data);
}
}

async isDirectory(path: string): Promise<boolean> {
// approximate a directory check
return !this.tree.exists(path) && this.tree.getDir(path).subfiles.length > 0;
Comment thread
dgp1130 marked this conversation as resolved.
}

async isFile(path: string): Promise<boolean> {
return this.tree.exists(path);
}
Comment thread
dgp1130 marked this conversation as resolved.
}

/**
* Updates the workspace file (`angular.json`) found within the root of the schematic's tree.
* The workspace object model can be directly modified within the provided updater function
* with changes being written to the workspace file after the updater function returns.
* The spacing and overall layout of the file (including comments) will be maintained where
* possible when updating the file.
*
* @param updater An update function that can be used to modify the object model for the
* workspace. A {@link WorkspaceDefinition} is provided as the first argument to the function.
*/
export function updateWorkspace(
updater: (workspace: workspaces.WorkspaceDefinition) => void | Rule | PromiseLike<void | Rule>,
): Rule;
export function updateWorkspace(workspace: workspaces.WorkspaceDefinition): Rule;
export function updateWorkspace(
updaterOrWorkspace:
| workspaces.WorkspaceDefinition
| ((workspace: workspaces.WorkspaceDefinition) => void | Rule | PromiseLike<void | Rule>),
updater: (workspace: WorkspaceDefinition) => void | Rule | PromiseLike<void | Rule>,
): Rule {
return async (tree: Tree) => {
const host = createHost(tree);
const host = new TreeWorkspaceHost(tree);

if (typeof updaterOrWorkspace === 'function') {
const { workspace } = await workspaces.readWorkspace('/', host);
const { workspace } = await workspaces.readWorkspace(DEFAULT_WORKSPACE_PATH, host);

const result = await updaterOrWorkspace(workspace);
const result = await updater(workspace);

await workspaces.writeWorkspace(workspace, host);
await workspaces.writeWorkspace(workspace, host);

return result || noop;
} else {
await workspaces.writeWorkspace(updaterOrWorkspace, host);

return noop;
}
return result || noop;
Comment thread
dgp1130 marked this conversation as resolved.
};
}

export async function getWorkspace(tree: Tree, path = '/') {
const host = createHost(tree);
// TODO: This should be renamed `readWorkspace` once deep imports are restricted (already exported from `utility` with that name)
/**
* Reads a workspace file (`angular.json`) from the provided {@link Tree} instance.
*
* @param tree A schematics {@link Tree} instance used to access the workspace file.
* @param path The path where a workspace file should be found. If a file is specified, the file
* path will be used. If a directory is specified, the file `angular.json` will be used from
* within the specified directory. Defaults to `/angular.json`.
* @returns A {@link WorkspaceDefinition} representing the workspace found at the specified path.
*/
export async function getWorkspace(
tree: Tree,
path = DEFAULT_WORKSPACE_PATH,
): Promise<WorkspaceDefinition> {
const host = new TreeWorkspaceHost(tree);

const { workspace } = await workspaces.readWorkspace(path, host);

return workspace;
}

/**
* Writes a workspace file (`angular.json`) to the provided {@link Tree} instance.
* The spacing and overall layout of an exisitng file (including comments) will be maintained where
* possible when writing the file.
*
* @param tree A schematics {@link Tree} instance used to access the workspace file.
* @param workspace The {@link WorkspaceDefinition} to write.
* @param path The path where a workspace file should be written. If a file is specified, the file
* path will be used. If not provided, the definition's underlying file path stored during reading
* will be used.
*/
export async function writeWorkspace(
tree: Tree,
workspace: WorkspaceDefinition,
path?: string,
): Promise<void> {
const host = new TreeWorkspaceHost(tree);

return workspaces.writeWorkspace(workspace, host, path);
}

/**
* Build a default project path for generating.
* @param project The project which will have its default path generated.
Expand Down
Loading