Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add workspace support #21

Merged
merged 10 commits into from
Dec 9, 2021
14 changes: 10 additions & 4 deletions bin/npm-template-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const checkPackage = require('../lib/postlint/check-package.js')
const checkGitIgnore = require('../lib/postlint/check-gitignore.js')
const getConfig = require('../lib/config.js')

const main = async () => {
const {
Expand All @@ -12,10 +13,15 @@ const main = async () => {
throw new Error('This package requires npm >7.21.1')
}

const problems = [
...(await checkPackage(root)),
...(await checkGitIgnore(root)),
]
const config = await getConfig(root)

const problemSets = []
for (const path of config.paths) {
problemSets.push(await checkPackage(path))
problemSets.push(await checkGitIgnore(path))
}

const problems = problemSets.flat()
wraithgar marked this conversation as resolved.
Show resolved Hide resolved

if (problems.length) {
console.error('Some problems were detected:')
Expand Down
13 changes: 8 additions & 5 deletions bin/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const copyContent = require('../lib/postinstall/copy-content.js')
const patchPackage = require('../lib/postinstall/update-package.js')
const getConfig = require('../lib/config.js')

const main = async () => {
const {
Expand All @@ -14,12 +15,14 @@ const main = async () => {
return
}

const needsAction = await patchPackage(root)
if (!needsAction) {
return
}
const config = await getConfig(root)
for (const path of config.paths) {
if (!await patchPackage(path)) {
continue
}

await copyContent(root)
await copyContent(path, root)
}
}

module.exports = main().catch((err) => {
Expand Down
47 changes: 47 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const PackageJson = require('@npmcli/package-json')
const mapWorkspaces = require('@npmcli/map-workspaces')

const defaultConfig = {
includeRoot: true,
workspaces: [],
paths: [],
}

module.exports = async (root) => {
let pkg
let pkgError = false
try {
pkg = (await PackageJson.load(root)).content
} catch (e) {
pkgError = true
}
if (pkgError || !pkg.templateOSS) {
return {
...defaultConfig,
paths: [root],
}
}
const config = {
...defaultConfig,
...pkg.templateOSS,
}
const workspaceMap = await mapWorkspaces({
pkg,
cwd: root,
})
const wsPaths = []
const workspaceSet = new Set(config.workspaces)
for (const [name, path] of workspaceMap.entries()) {
if (workspaceSet.has(name)) {
wsPaths.push(path)
}
}
config.workspacePaths = wsPaths

if (config.includeRoot) {
config.paths.push(root)
}
config.paths = config.paths.concat(config.workspacePaths)

return config
}
76 changes: 76 additions & 0 deletions lib/content/ci-workspace.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Node Workspace CI %%pkgname%%

on:
pull_request:
paths:
- %%pkgpath%%/**
branches:
- '*'
push:
paths:
- %%pkgpath%%/**
branches:
- release-next
- latest
workflow_dispatch:

jobs:
lint:
runs-on: ubuntu-latest
steps:
# Checkout the npm/cli repo
- uses: actions/checkout@v2
- name: Use Node.js 14.x
fritzy marked this conversation as resolved.
Show resolved Hide resolved
uses: actions/setup-node@v2
with:
node-version: 14.x
cache: npm
- name: Install dependencies
run: |
node ./bin/npm-cli.js install --ignore-scripts --no-audit
node ./bin/npm-cli.js rebuild
- name: Run linting
run: node ./bin/npm-cli.js run posttest -w libnpmdiff
env:
DEPLOY_VERSION: testing

test:
strategy:
fail-fast: false
matrix:
node-version: ['12.13.0', 12.x, '14.15.0', 14.x, '16.0.0', 16.x]
platform:
- os: ubuntu-latest
shell: bash
- os: macos-latest
shell: bash
- os: windows-latest
shell: bash
- os: windows-latest
shell: powershell

runs-on: ${{ matrix.platform.os }}
defaults:
run:
shell: ${{ matrix.platform.shell }}

steps:
# Checkout the npm/cli repo
- uses: actions/checkout@v2

# Installs the specific version of Node.js
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: npm

# Run the installer script
- name: Install dependencies
run: |
node ./bin/npm-cli.js install --ignore-scripts --no-audit
node ./bin/npm-cli.js rebuild

# Run the tests, but not if we're just gonna do coveralls later anyway
- name: Run Tap tests
run: node ./bin/npm-cli.js run -w libnpmdiff --ignore-scripts test -- -t600 -Rbase -c
80 changes: 62 additions & 18 deletions lib/postinstall/copy-content.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,43 @@
const { dirname, join, resolve } = require('path')
const fs = require('@npmcli/fs')
const { readFile, writeFile } = require('fs/promises')
const PackageJson = require('@npmcli/package-json')

const contentDir = resolve(__dirname, '..', 'content')

// keys are destination paths in the target project
// values are paths to contents relative to '../content/'
const content = {
fritzy marked this conversation as resolved.
Show resolved Hide resolved
'.eslintrc.js': './eslintrc.js',
'.gitignore': './gitignore',
'LICENSE.md': './LICENSE.md',
'SECURITY.md': './SECURITY.md',
}

const rootContent = {
'.github/workflows/ci.yml': './ci.yml',
'.github/ISSUE_TEMPLATE/bug.yml': './bug.yml',
'.github/ISSUE_TEMPLATE/config.yml': './config.yml',
'.github/CODEOWNERS': './CODEOWNERS',
'.gitignore': './gitignore',
'LICENSE.md': './LICENSE.md',
'SECURITY.md': './SECURITY.md',
}

// currently no workspace content
// const workspaceContent = {}
// const workspaceRootContent = {}

const filesToDelete = [
// remove any other license files
/^LICENSE*/,
// remove any eslint config files that aren't local to the project
/^\.eslintrc\.(?!(local\.)).*/,
]

// given a root directory, copy all files in the content map
// after purging any files we need to delete
const copyContent = async (root) => {
const contents = await fs.readdir(root)

for (const file of contents) {
if (filesToDelete.some((p) => p.test(file))) {
await fs.rm(join(root, file))
}
}

for (let [target, source] of Object.entries(content)) {
const copyFiles = async (targetDir, files) => {
for (let [target, source] of Object.entries(files)) {
source = join(contentDir, source)
target = join(root, target)
// if the target is a subdirectory of the root, mkdirp it first
if (dirname(target) !== root) {
target = join(targetDir, target)
// if the target is a subdirectory of the path, mkdirp it first
if (dirname(target) !== targetDir) {
await fs.mkdir(dirname(target), {
owner: 'inherit',
recursive: true,
Expand All @@ -48,6 +47,51 @@ const copyContent = async (root) => {
await fs.copyFile(source, target, { owner: 'inherit' })
}
}

// given a root directory, copy all files in the content map
// after purging any files we need to delete
const copyContent = async (path, rootPath) => {
const contents = await fs.readdir(path)

const isWorkspace = path !== rootPath

for (const file of contents) {
if (filesToDelete.some((p) => p.test(file))) {
await fs.rm(join(path, file))
}
}

await copyFiles(path, content)
if (!isWorkspace) {
await copyFiles(rootPath, rootContent)
return
}

// isWorkspace === true
// if we had workspace specific content...
// await copyFiles(path, workspaceContent)
// await copyFiles(rootPath, workspaceRootContent)

const workspacePkg = (await PackageJson.load(path)).content
const workspaceName = workspacePkg.name
const workflowPath = join(rootPath, '.github', 'workflows')
await fs.mkdir(workflowPath, {
owner: 'inherit',
recursive: true,
force: true,
})

let workflowData = await readFile(
join(contentDir, './ci-workspace.yml'),
{ encoding: 'utf-8' }
)

workflowData = workflowData.replace(/%%pkgpath%%/g, path)
workflowData = workflowData.replace(/%%pkgname%%/g, workspaceName)

await writeFile(join(workflowPath, `ci-${workspaceName}.yml`), workflowData)
}
copyContent.content = content
copyContent.rootContent = rootContent

module.exports = copyContent
Loading