-
Notifications
You must be signed in to change notification settings - Fork 623
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5b0ff30
commit 24b925e
Showing
4 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,41 @@ | ||
name: workspace publish | ||
|
||
on: | ||
push: | ||
branches: [main, workspace_publish] | ||
|
||
env: | ||
DENO_UNSTABLE_WORKSPACES: true | ||
|
||
jobs: | ||
publish: | ||
runs-on: ubuntu-22.04 | ||
timeout-minutes: 30 | ||
|
||
permissions: | ||
contents: read | ||
id-token: write | ||
|
||
steps: | ||
- name: Clone repository | ||
uses: actions/checkout@v4 | ||
with: | ||
submodules: true | ||
|
||
- name: Set up Deno | ||
uses: denoland/setup-deno@v1 | ||
with: | ||
deno-version: cdbf902499dc62a4fb9f8cbf2a47a7b446623929 | ||
|
||
- name: Convert to workspace | ||
run: deno run -A ./_tools/convert_to_workspace.ts | ||
|
||
- name: Format | ||
run: deno fmt | ||
|
||
- name: Type check | ||
run: deno test --unstable --no-run --doc | ||
|
||
- name: Publish (workspace publish or tag push) | ||
if: github.ref == 'refs/heads/workspace_publish' || startsWith(github.ref, 'refs/tags/') | ||
run: deno do-not-use-publish . |
This file contains 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,233 @@ | ||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. | ||
|
||
import { discoverPackages, discoverExportsByPackage} from "./packages.ts"; | ||
import { walk } from "../fs/walk.ts"; | ||
import { | ||
dirname, | ||
fromFileUrl, | ||
join, | ||
relative, | ||
toFileUrl, | ||
} from "../path/mod.ts"; | ||
import { VERSION } from "../version.ts"; | ||
|
||
const cwd = await Deno.realPath("."); | ||
|
||
await Deno.remove("./version.ts"); | ||
|
||
const toRemove = `1. Do not import symbols with an underscore in the name. | ||
Bad: | ||
\`\`\`ts | ||
import { _format } from "https://deno.land/std@$STD_VERSION/path/_common/format.ts"; | ||
\`\`\` | ||
1. Do not import modules with an underscore in the path. | ||
Bad: | ||
\`\`\`ts | ||
import { filterInPlace } from "https://deno.land/std@$STD_VERSION/collections/_utils.ts"; | ||
\`\`\` | ||
1. Do not import test modules or test data. | ||
Bad: | ||
\`\`\`ts | ||
import { test } from "https://deno.land/std@$STD_VERSION/front_matter/test.ts"; | ||
\`\`\` | ||
`; | ||
let readme = await Deno.readTextFile("README.md"); | ||
readme = readme.replace(toRemove, ""); | ||
await Deno.writeTextFile("README.md", readme); | ||
|
||
let fileServer = await Deno.readTextFile("http/file_server.ts"); | ||
fileServer = fileServer.replace( | ||
`import { VERSION } from "../version.ts";`, | ||
`import { version } from "./deno.json" assert { type: "json" };`, | ||
); | ||
fileServer = fileServer.replaceAll("${VERSION}", "${version}"); | ||
fileServer = fileServer.replace( | ||
"https://deno.land/std/http/file_server.ts", | ||
"jsr:@std/http@${version}/file_server", | ||
); | ||
await Deno.writeTextFile("http/file_server.ts", fileServer); | ||
|
||
let fileServerTest = await Deno.readTextFile("http/file_server_test.ts"); | ||
fileServerTest = fileServerTest.replace( | ||
`import { VERSION } from "../version.ts";`, | ||
`import { version } from "./deno.json" assert { type: "json" };`, | ||
); | ||
fileServerTest = fileServerTest.replaceAll("${VERSION}", "${version}"); | ||
await Deno.writeTextFile("http/file_server_test.ts", fileServerTest); | ||
|
||
const packages = await discoverPackages(); | ||
const exportsByPackage = await discoverExportsByPackage(packages); | ||
|
||
const allExports: string[] = []; | ||
for (const [pkg, exports] of exportsByPackage.entries()) { | ||
for (const [_, path] of exports) { | ||
allExports.push(join(pkg, path)); | ||
} | ||
} | ||
|
||
// can't use a data url here because it's too long and won't work on windows | ||
const tempFileText = allExports.map((path) => `import "${toFileUrl(Deno.realPathSync(path))}";`) | ||
.join("") | ||
const tempFilePath = "temp_graph.ts"; | ||
Deno.writeTextFileSync(tempFilePath, tempFileText); | ||
const out = await new Deno.Command(Deno.execPath(), { | ||
args: ["info", "--json", "--config", "deno.json", tempFilePath], | ||
}).output(); | ||
Deno.removeSync(tempFilePath); | ||
const graph = JSON.parse(new TextDecoder().decode(out.stdout)); | ||
|
||
const pkgDeps = new Map<string, Set<string>>( | ||
packages.map((pkg) => [pkg, new Set()]), | ||
); | ||
for (const { specifier, dependencies } of graph.modules) { | ||
if (!specifier.startsWith("file://") || specifier.endsWith("temp_graph.ts")) continue; | ||
const from = relative(cwd, fromFileUrl(specifier)).replaceAll("\\", "/"); | ||
const fromPkg = from.split("/")[0]; | ||
for (const dep of dependencies ?? []) { | ||
if (dep.code) { | ||
const to = relative(cwd, fromFileUrl(dep.code.specifier)).replaceAll("\\", "/"); | ||
const toPkg = to.split("/")[0]; | ||
if (fromPkg !== toPkg) { | ||
pkgDeps.get(fromPkg)!.add(toPkg); | ||
} | ||
} | ||
if (dep.types) { | ||
const to = relative(cwd, fromFileUrl(dep.types.specifier)).replaceAll("\\", "/"); | ||
const toPkg = to.split("/")[0]; | ||
if (fromPkg !== toPkg) { | ||
pkgDeps.get(fromPkg)!.add(toPkg); | ||
} | ||
} | ||
} | ||
} | ||
|
||
const orderedPackages: string[] = []; | ||
const seen = new Set<string>(); | ||
function visit(pkg: string) { | ||
if (seen.has(pkg)) return; | ||
seen.add(pkg); | ||
for (const dep of pkgDeps.get(pkg)!) { | ||
visit(dep); | ||
} | ||
orderedPackages.push(pkg); | ||
} | ||
for (const pkg of packages) { | ||
visit(pkg); | ||
} | ||
|
||
// Now walk through all files, and replace relative imports between packages | ||
// with absolute jsr imports like so: | ||
// ``` | ||
// // cli/parse_args.ts | ||
// import { assert } from "../assert/assert.ts"; | ||
// import * as path from "../path/mod.ts"; | ||
// ``` | ||
// becomes | ||
// ``` | ||
// // cli/parse_args.ts | ||
// import { assert } from "@std/assert/assert"; | ||
// import * as path from "@std/path"; | ||
// ``` | ||
// Also replace all absolute https://deno.land/std@$STD_VERSION/ imports with absolute jsr | ||
// imports. | ||
for await (const entry of walk(cwd)) { | ||
if (!entry.isFile) continue; | ||
if (entry.path.includes("/_tools")) continue; // ignore tools | ||
if (entry.path.includes("/testdata/")) continue; // ignore testdata | ||
|
||
if (!entry.path.endsWith(".md") && !entry.path.endsWith(".ts")) continue; | ||
const text = await Deno.readTextFile(entry.path); | ||
const currentUrl = toFileUrl(entry.path); | ||
const currentPkg = relative(cwd, entry.path).replaceAll("\\", "/").split("/")[0]; | ||
|
||
// Find all relative imports. | ||
const relativeImportRegex = /from\s+["']\.?\.\/([^"']+)["']/g; | ||
const relativeImports = []; | ||
for (const match of text.matchAll(relativeImportRegex)) { | ||
relativeImports.push("../" + match[1]); | ||
} | ||
|
||
// Find all absolute imports. | ||
const absoluteImportRegex = | ||
/https:\/\/deno\.land\/std@\$STD_VERSION\/([^"'\s]+)/g; | ||
const absoluteImports = []; | ||
for (const match of text.matchAll(absoluteImportRegex)) { | ||
absoluteImports.push("https://deno.land/std@$STD_VERSION/" + match[1]); | ||
} | ||
|
||
const replacedImports: [string, string][] = []; | ||
|
||
for (const specifier of relativeImports) { | ||
const targetUrl = new URL(specifier, currentUrl); | ||
const path = fromFileUrl(targetUrl); | ||
const target = relative(cwd, path).replaceAll("\\", "/"); | ||
const pkg = target.split("/")[0]; | ||
if (pkg === currentPkg) { | ||
let newSpecifier = relative(dirname(entry.path), target).replaceAll("\\", "/"); | ||
if (!newSpecifier.startsWith(".")) { | ||
newSpecifier = "./" + newSpecifier; | ||
} | ||
replacedImports.push([specifier, newSpecifier]); | ||
} else { | ||
const newSpecifier = "@std/" + | ||
target.replace(/(\.d)?\.ts$/, "").replace(/\/mod$/, ""); | ||
replacedImports.push([specifier, newSpecifier]); | ||
} | ||
} | ||
|
||
for (const specifier of absoluteImports) { | ||
const target = specifier.replace( | ||
/^https:\/\/deno\.land\/std@\$STD_VERSION\//, | ||
"", | ||
); | ||
const newSpecifier = "@std/" + | ||
target.replace(/(\.d)?\.ts$/, "").replace(/\/mod$/, ""); | ||
replacedImports.push([specifier, newSpecifier]); | ||
} | ||
|
||
// Replace all imports. | ||
let newText = text; | ||
for (const [oldSpecifier, newSpecifier] of replacedImports) { | ||
newText = newText.replace(oldSpecifier, newSpecifier); | ||
} | ||
|
||
// Write the file back. | ||
await Deno.writeTextFile(entry.path, newText); | ||
} | ||
|
||
// Generate `$package/deno.json` files. | ||
for (const pkg of packages) { | ||
const exportsList = exportsByPackage.get(pkg)!; | ||
let exports; | ||
if (exportsList.length === 1 && exportsList[0][0] === ".") { | ||
exports = "./mod.ts"; | ||
} else { | ||
exports = Object.fromEntries(exportsList); | ||
} | ||
const denoJson = { | ||
name: `@std/${pkg}`, | ||
version: VERSION, | ||
exports, | ||
}; | ||
await Deno.writeTextFile( | ||
join(pkg, "deno.json"), | ||
JSON.stringify(denoJson, null, 2) + "\n", | ||
); | ||
} | ||
|
||
// Generate `deno.json` file. | ||
const denoJson = JSON.parse(await Deno.readTextFile("deno.json")); | ||
denoJson.workspaces = orderedPackages.map((pkg) => `./${pkg}`); | ||
for (const pkg of packages) { | ||
denoJson.imports[`@std/${pkg}`] = `jsr:@std/${pkg}@^${VERSION}`; | ||
denoJson.imports[`@std/${pkg}/`] = `jsr:/@std/${pkg}@^${VERSION}/`; | ||
} | ||
await Deno.writeTextFile( | ||
"deno.json", | ||
JSON.stringify(denoJson, null, 2) + "\n", | ||
); |
This file contains 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,49 @@ | ||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. | ||
|
||
import { discoverExportsByPackage, discoverPackages } from "./packages.ts"; | ||
import { join } from "../path/mod.ts"; | ||
import $ from "https://deno.land/x/dax@0.36.0/mod.ts"; | ||
|
||
const packages = await discoverPackages(); | ||
const exportsByPackage = await discoverExportsByPackage(packages); | ||
|
||
const paths = []; | ||
for (const pkg of packages) { | ||
const exports = exportsByPackage.get(pkg)!; | ||
for (const [_name, path] of exports) { | ||
if (path.endsWith(".json")) { | ||
continue; | ||
} | ||
paths.push(join(pkg, path)); | ||
} | ||
} | ||
|
||
paths.push("./types.d.ts"); | ||
|
||
const text = await $`deno doc --lint ${paths}`.printCommand().env( | ||
"NO_COLOR", | ||
"1", | ||
).noThrow().captureCombined().text(); | ||
const lines = text.split("\n"); | ||
let currentMessageCount = 0; | ||
let currentJsDocCount = 0; | ||
for (let i = 0; i < lines.length; i++) { | ||
const line = lines[i]; | ||
if (line.includes("Missing JSDoc comment.")) { | ||
currentJsDocCount++; | ||
currentMessageCount++; | ||
} else if (line.trimStart().startsWith("at ")) { | ||
if (currentJsDocCount !== currentMessageCount) { | ||
console.log(line); | ||
} else { | ||
i++; // skip a blank line | ||
} | ||
currentJsDocCount = 0; | ||
currentMessageCount = 0; | ||
} else { | ||
if (line.trim().length > 0) { | ||
currentMessageCount++; | ||
} | ||
console.log(line); | ||
} | ||
} |
This file contains 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,60 @@ | ||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. | ||
|
||
import { walk } from "../fs/walk.ts"; | ||
import { relative } from "../path/mod.ts"; | ||
|
||
export async function discoverPackages() { | ||
const packages = []; | ||
for await (const entry of Deno.readDir(".")) { | ||
if ( | ||
entry.isDirectory && !entry.name.startsWith(".") && | ||
!entry.name.startsWith("_") && entry.name !== "coverage" | ||
) { | ||
packages.push(entry.name); | ||
} | ||
} | ||
packages.sort(); | ||
|
||
console.log("Discovered", packages.length, "packages."); | ||
return packages; | ||
} | ||
|
||
export async function discoverExportsByPackage(packages: string[]) { | ||
// Collect all of the exports for each package. | ||
const exportsByPackage = new Map<string, [string, string][]>(); | ||
for (const pkg of packages) { | ||
const exports = await discoverExports(pkg); | ||
exportsByPackage.set(pkg, exports); | ||
} | ||
return exportsByPackage; | ||
} | ||
|
||
async function discoverExports(pkg: string) { | ||
const exports: [string, string][] = []; | ||
const base = await Deno.realPath(pkg); | ||
const files = walk(base); | ||
for await (const file of files) { | ||
if (!file.isFile) continue; | ||
const path = "/" + relative(base, file.path).replaceAll("\\", "/"); | ||
const name = path.replace(/(\.d)?\.ts$/, ""); | ||
if (name === path && !name.endsWith(".json")) continue; // not a typescript | ||
if (name.includes("/.") || name.includes("/_")) continue; // hidden/internal files | ||
if ( | ||
(name.endsWith("_test") || name.endsWith("/test")) && | ||
!(name === "/test" && pkg === "front_matter") | ||
) continue; // test files | ||
if (name.includes("/example/") || name.endsWith("_example")) continue; // example files | ||
if (name.includes("/testdata/")) continue; // testdata files | ||
if (name.endsWith("/deno.json")) continue; // deno.json files | ||
|
||
const key = "." + name.replace(/\/mod$/, ""); | ||
exports.push([key, "." + path]); | ||
} | ||
exports.sort((a, b) => a[0].localeCompare(b[0])); | ||
return exports; | ||
} | ||
|
||
if (import.meta.main) { | ||
const packages = await discoverPackages(); | ||
console.log(packages); | ||
} |