Skip to content

Commit de64434

Browse files
API Change detection (#1986)
* Warn when we break APIs can be run like so `yarn diff-ts -f packages/@react-spectrum/button/**/*.d.ts -i` * save point * Use the docs transformer to build our API output * update yarn lock * couple minor updates to make it ready * remove unused dep * add comments and include reat-aria/stately Co-authored-by: Danni <[email protected]>
1 parent 43d5653 commit de64434

File tree

9 files changed

+501
-4
lines changed

9 files changed

+501
-4
lines changed

.parcelrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"extends": "@parcel/config-default",
33
"resolvers": ["parcel-resolver-docs", "..."],
44
"transformers": {
5+
"apiCheck:*.{js,ts,tsx,json}": ["parcel-transformer-docs"],
56
"docs:*.{js,ts,tsx,json}": ["parcel-transformer-docs", "@parcel/transformer-inline"],
67
"docs-json:*.{js,ts,tsx,json}": ["parcel-transformer-docs"],
78
"*.{md,mdx}": ["parcel-transformer-mdx-docs"],

.storybook/theme.register.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {themes} from '@storybook/theming';
22
import addons from '@storybook/addons';
33
import {FORCE_RE_RENDER} from '@storybook/core-events';
44
// temporary until we have a better place to grab it from
5-
import * as packageJSON from '../packages/@react-spectrum/alert/package.json';
5+
import * as packageJSON from '../packages/@adobe/react-spectrum/package.json';
66

77
// Automatically switch light/dark theme based on system pref.
88
addons.register('theme-switcher', api => {

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"start:docs": "PARCEL_WORKER_BACKEND=process DOCS_ENV=dev parcel 'packages/@react-{spectrum,aria,stately}/*/docs/*.mdx' 'packages/dev/docs/pages/**/*.mdx'",
2525
"build:docs": "PARCEL_WORKER_BACKEND=process DOCS_ENV=staging parcel build 'packages/@react-{spectrum,aria,stately}/*/docs/*.mdx' 'packages/dev/docs/pages/**/*.mdx' --no-scope-hoist",
2626
"test": "yarn jest",
27+
"build": "make build",
2728
"test:ssr": "yarn jest --config jest.ssr.config.js",
2829
"ci-test": "yarn jest --maxWorkers=2 && yarn test:ssr --runInBand",
2930
"ci-test-17": "yarn jest --maxWorkers=2 && yarn test:ssr --runInBand",
@@ -37,7 +38,11 @@
3738
"chromatic": "chromatic --project-token 'q5msektqrfg' --build-script-name 'build:chromatic'",
3839
"merge:css": "babel-node --presets @babel/env ./scripts/merge-spectrum-css.js",
3940
"release": "lerna publish from-package --yes",
40-
"publish:nightly": "lerna publish -y --canary --preid nightly --dist-tag=nightly --exact --force-publish=* --no-push"
41+
"publish:nightly": "lerna publish -y --canary --preid nightly --dist-tag=nightly --exact --force-publish=* --no-push",
42+
"build:api-published": "node scripts/buildPublishedAPI.js",
43+
"build:api-branch": "node scripts/buildBranchAPI.js",
44+
"compare:apis": "node scripts/compareAPIs.js",
45+
"check-apis": "yarn build:api-published && yarn build:api-branch && yarn compare:apis"
4146
},
4247
"workspaces": [
4348
"packages/react-stately",
@@ -115,13 +120,15 @@
115120
"eslint-plugin-rulesdir": "^0.1.0",
116121
"fast-glob": "^3.1.0",
117122
"file-loader": "^0.9.0",
123+
"fs-extra": "^10.0.0",
118124
"full-icu": "^1.3.0",
119125
"identity-obj-proxy": "^3.0.0",
120126
"ignore-styles": "^5.0.1",
121127
"jest": "^26.4.2",
122128
"jest-junit": "^12.0.0",
123129
"jest-matchmedia-mock": "^1.0.0",
124130
"jsdom": "^16.3.0",
131+
"json-diff-ts": "^1.1.0",
125132
"lerna": "^3.13.2",
126133
"lfcdn": "^0.4.2",
127134
"md5": "^2.2.1",
@@ -158,6 +165,7 @@
158165
"tempy": "^0.5.0",
159166
"typescript": "^3.8.3",
160167
"url-loader": "^1.1.2",
168+
"walk-object": "^4.0.0",
161169
"webpack": "^4.44.2",
162170
"webpack-dev-middleware": "^3.6.1",
163171
"webpack-hot-middleware": "^2.24.3",

packages/dev/parcel-resolver-docs/DocsResolver.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const path = require('path');
1616

1717
module.exports = new Resolver({
1818
async resolve({dependency, options, filePath}) {
19-
if (dependency.moduleSpecifier.startsWith('docs:') || dependency.pipeline === 'docs' || dependency.pipeline === 'docs-json') {
19+
if (dependency.moduleSpecifier.startsWith('docs:') || dependency.moduleSpecifier.startsWith('apiCheck:') || dependency.pipeline === 'docs' || dependency.pipeline === 'docs-json' || dependency.pipeline === 'apiCheck') {
2020
const resolver = new NodeResolver({
2121
extensions: ['ts', 'tsx', 'd.ts', 'js'],
2222
mainFields: ['source', 'types', 'main'],
@@ -33,7 +33,10 @@ module.exports = new Resolver({
3333
if (resolved) {
3434
// HACK: ensure source code is used to build types, not compiled code.
3535
// Parcel removes the source field from package.json when the code comes from node_modules.
36-
if (resolved.filePath.endsWith('.d.ts') && !resolved.filePath.includes('@react-types')) {
36+
if ((
37+
/^@(react-spectrum|react-aria|react-stately|internationalized|react-types|spectrum-icons|adobe\/react-spectrum)/g.test(resolved.filePath)
38+
|| /^(react-aria|react-stately)/g.test(resolved.filePath)
39+
) && resolved.filePath.endsWith('.d.ts') && !resolved.filePath.includes('@react-types')) {
3740
resolved.filePath = path.resolve(path.dirname(resolved.filePath), '..', 'src', 'index.ts');
3841
}
3942

packages/dev/parcel-transformer-docs/DocsTransformer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ module.exports = new Transformer({
5656
exports[path.node.declaration.id.name] = processExport(path.get('declaration'));
5757
} else {
5858
let identifiers = t.getBindingIdentifiers(path.node.declaration);
59+
let index = 0;
5960
for (let id of Object.keys(identifiers)) {
61+
exports[identifiers[id].name] = processExport(path.get('declaration.declarations')[index]);
62+
index += 1;
6063
asset.symbols.set(identifiers[id].name, identifiers[id].name);
6164
}
6265
}

scripts/buildBranchAPI.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
const tempy = require('tempy');
14+
const fs = require('fs-extra');
15+
const packageJSON = require('../package.json');
16+
const path = require('path');
17+
const glob = require('fast-glob');
18+
const spawn = require('cross-spawn');
19+
20+
build().catch(err => {
21+
console.error(err.stack);
22+
process.exit(1);
23+
});
24+
25+
/**
26+
* Building this will run the docs builder using the apiCheck pipeline in .parcelrc
27+
* This will generate json containing the visible (API/exposed) type definitions for each package
28+
* This is run against the current branch by copying the current branch into a temporary directory and building there
29+
*/
30+
async function build() {
31+
// Create a temp directory to build the site in
32+
let dir = tempy.directory();
33+
console.log(`Building branch api into ${dir}...`);
34+
35+
// Generate a package.json containing just what we need to build the website
36+
let pkg = {
37+
name: 'rsp-website',
38+
version: '0.0.0',
39+
private: true,
40+
workspaces: [
41+
'packages/*/*'
42+
],
43+
devDependencies: Object.fromEntries(
44+
Object.entries(packageJSON.devDependencies)
45+
.filter(([name]) =>
46+
name.startsWith('@parcel') ||
47+
name === 'parcel' ||
48+
name === 'patch-package' ||
49+
name.startsWith('@spectrum-css') ||
50+
name.startsWith('postcss') ||
51+
name.startsWith('@adobe')
52+
)
53+
),
54+
dependencies: {},
55+
resolutions: packageJSON.resolutions,
56+
browserslist: packageJSON.browserslist,
57+
scripts: {
58+
build: 'yarn parcel build packages/@react-spectrum/actiongroup',
59+
postinstall: 'patch-package'
60+
}
61+
};
62+
63+
// Add dependencies on each published package to the package.json, and
64+
// copy the docs from the current package into the temp dir.
65+
let packagesDir = path.join(__dirname, '..', 'packages');
66+
let packages = glob.sync('*/**/package.json', {cwd: packagesDir});
67+
68+
pkg.devDependencies['babel-plugin-transform-glob-import'] = '*';
69+
70+
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(pkg, false, 2));
71+
72+
fs.writeFileSync(path.join(dir, 'babel.config.json'), `{
73+
"plugins": [
74+
"transform-glob-import"
75+
]
76+
}`);
77+
78+
// Copy necessary code and configuration over
79+
fs.copySync(path.join(__dirname, '..', 'yarn.lock'), path.join(dir, 'yarn.lock'));
80+
fs.copySync(path.join(__dirname, '..', 'packages', 'dev'), path.join(dir, 'packages', 'dev'));
81+
fs.removeSync(path.join(dir, 'packages', 'dev', 'v2-test-deps'));
82+
fs.copySync(path.join(__dirname, '..', 'packages', '@adobe', 'spectrum-css-temp'), path.join(dir, 'packages', '@adobe', 'spectrum-css-temp'));
83+
fs.copySync(path.join(__dirname, '..', '.parcelrc'), path.join(dir, '.parcelrc'));
84+
fs.copySync(path.join(__dirname, '..', 'cssnano.config.js'), path.join(dir, 'cssnano.config.js'));
85+
fs.copySync(path.join(__dirname, '..', 'postcss.config.js'), path.join(dir, 'postcss.config.js'));
86+
fs.copySync(path.join(__dirname, '..', 'lib'), path.join(dir, 'lib'));
87+
fs.copySync(path.join(__dirname, '..', 'CONTRIBUTING.md'), path.join(dir, 'CONTRIBUTING.md'));
88+
89+
// Only copy babel patch over
90+
let patches = fs.readdirSync(path.join(__dirname, '..', 'patches'));
91+
let babelPatch = patches.find(name => name.startsWith('@babel'));
92+
fs.copySync(path.join(__dirname, '..', 'patches', babelPatch), path.join(dir, 'patches', babelPatch));
93+
94+
// Copy packages over to temp dir
95+
console.log('copying over');
96+
for (let p of packages) {
97+
if (!p.includes('spectrum-css') && !p.includes('dev/')) {
98+
let json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'packages', p)), 'utf8');
99+
if (json.private) {
100+
continue;
101+
}
102+
fs.copySync(path.join(__dirname, '..', 'packages', path.dirname(p)), path.join(dir, 'packages', path.dirname(p)));
103+
if (!p.includes('@react-types')) {
104+
delete json.types;
105+
}
106+
delete json.main;
107+
delete json.module;
108+
delete json.devDependencies;
109+
json.apiCheck = 'dist/api.json';
110+
json.targets = {
111+
apiCheck: {}
112+
};
113+
fs.writeFileSync(path.join(dir, 'packages', p), JSON.stringify(json, false, 2));
114+
}
115+
}
116+
117+
// Install dependencies from npm
118+
await run('yarn', {cwd: dir, stdio: 'inherit'});
119+
120+
// Build the website
121+
console.log('building api files');
122+
await run('yarn', ['parcel', 'build', 'packages/@react-{spectrum,aria,stately}/*/', 'packages/@internationalized/*/', '--target', 'apiCheck'], {cwd: dir, stdio: 'inherit'});
123+
124+
// Copy the build back into dist, and delete the temp dir.
125+
fs.copySync(path.join(dir, 'packages'), path.join(__dirname, '..', 'dist', 'branch-api'));
126+
fs.removeSync(dir);
127+
}
128+
129+
function run(cmd, args, opts) {
130+
return new Promise((resolve, reject) => {
131+
let child = spawn(cmd, args, opts);
132+
child.on('error', reject);
133+
child.on('close', code => {
134+
if (code !== 0) {
135+
reject(new Error('Child process failed'));
136+
return;
137+
}
138+
139+
resolve();
140+
});
141+
});
142+
}

0 commit comments

Comments
 (0)