@fuman/build 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +108 -0
- package/ci/github-actions.d.ts +3 -0
- package/ci/github-actions.js +28 -0
- package/ci/index.d.ts +1 -0
- package/cli/commands/_utils.d.ts +8 -0
- package/cli/commands/_utils.js +18 -0
- package/cli/commands/build.d.ts +20 -0
- package/cli/commands/build.js +45 -0
- package/cli/commands/bump-version.d.ts +18 -0
- package/cli/commands/bump-version.js +72 -0
- package/cli/commands/cr.d.ts +17 -0
- package/cli/commands/cr.js +76 -0
- package/cli/commands/find-changed-packages.d.ts +12 -0
- package/cli/commands/find-changed-packages.js +44 -0
- package/cli/commands/gen-changelog.d.ts +12 -0
- package/cli/commands/gen-changelog.js +49 -0
- package/cli/commands/gen-deps-graph.d.ts +15 -0
- package/cli/commands/gen-deps-graph.js +78 -0
- package/cli/commands/jsr.d.ts +6 -0
- package/cli/commands/jsr.js +79 -0
- package/cli/commands/publish.d.ts +48 -0
- package/cli/commands/publish.js +197 -0
- package/cli/commands/release.d.ts +34 -0
- package/cli/commands/release.js +226 -0
- package/cli/commands/validate-workspace-deps.d.ts +38 -0
- package/cli/commands/validate-workspace-deps.js +68 -0
- package/cli/index.d.ts +3 -0
- package/cli/main.d.ts +2 -0
- package/config.d.ts +32 -0
- package/fuman-build.d.ts +1 -0
- package/fuman-build.js +33 -0
- package/git/github.d.ts +14 -0
- package/git/github.js +48 -0
- package/git/index.d.ts +1 -0
- package/git/utils.d.ts +38 -0
- package/git/utils.js +110 -0
- package/index.d.ts +7 -0
- package/index.js +46 -0
- package/jsr/build-jsr.d.ts +7 -0
- package/jsr/build-jsr.js +145 -0
- package/jsr/config.d.ts +48 -0
- package/jsr/create-packages.d.ts +8 -0
- package/jsr/create-packages.js +46 -0
- package/jsr/deno-json.d.ts +19 -0
- package/jsr/deno-json.js +80 -0
- package/jsr/generate-workspace.d.ts +9 -0
- package/jsr/generate-workspace.js +174 -0
- package/jsr/index.d.ts +5 -0
- package/jsr/populate.d.ts +45 -0
- package/jsr/populate.js +132 -0
- package/jsr/utils/external-libs.d.ts +8 -0
- package/jsr/utils/external-libs.js +46 -0
- package/jsr/utils/index.d.ts +4 -0
- package/jsr/utils/jsr-api.d.ts +23 -0
- package/jsr/utils/jsr-api.js +115 -0
- package/jsr/utils/jsr-json.d.ts +10 -0
- package/jsr/utils/jsr-json.js +80 -0
- package/jsr/utils/jsr.d.ts +11 -0
- package/jsr/utils/jsr.js +74 -0
- package/jsr.d.ts +1 -0
- package/jsr.js +23 -0
- package/misc/_config.d.ts +1 -0
- package/misc/_config.js +18 -0
- package/misc/exec.d.ts +9 -0
- package/misc/exec.js +40 -0
- package/misc/fs.d.ts +4 -0
- package/misc/fs.js +32 -0
- package/misc/index.d.ts +4 -0
- package/misc/path.d.ts +1 -0
- package/misc/path.js +12 -0
- package/misc/publish-order.d.ts +3 -0
- package/misc/publish-order.js +56 -0
- package/misc/tsconfig.d.ts +2 -0
- package/misc/tsconfig.js +28 -0
- package/npm/index.d.ts +1 -0
- package/npm/npm-api.d.ts +7 -0
- package/npm/npm-api.js +18 -0
- package/package-json/collect-package-jsons.d.ts +8 -0
- package/package-json/collect-package-jsons.js +61 -0
- package/package-json/find-package-json.d.ts +7 -0
- package/package-json/find-package-json.js +28 -0
- package/package-json/index.d.ts +5 -0
- package/package-json/parse.d.ts +4 -0
- package/package-json/parse.js +40 -0
- package/package-json/process-package-json.d.ts +13 -0
- package/package-json/process-package-json.js +132 -0
- package/package-json/types.d.ts +56 -0
- package/package-json/types.js +57 -0
- package/package-json/utils.d.ts +4 -0
- package/package-json/utils.js +27 -0
- package/package.json +67 -0
- package/versioning/bump-version.d.ts +48 -0
- package/versioning/bump-version.js +129 -0
- package/versioning/collect-files.d.ts +22 -0
- package/versioning/collect-files.js +66 -0
- package/versioning/generate-changelog.d.ts +13 -0
- package/versioning/generate-changelog.js +81 -0
- package/versioning/types.d.ts +32 -0
- package/vite/build-plugin.d.ts +73 -0
- package/vite/build-plugin.js +170 -0
- package/vite/config.d.ts +34 -0
- package/vite/index.d.ts +2 -0
- package/vite.d.ts +1 -0
- package/vite.js +4 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PackageJson } from '../package-json/types.js';
|
|
2
|
+
export interface DenoJson {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
exports?: Record<string, string>;
|
|
6
|
+
imports?: Record<string, string>;
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
publish?: {
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare function packageJsonToDeno({ packageJson, packageJsonOrig, workspaceVersions, exclude, buildDirName, baseDir, }: {
|
|
13
|
+
packageJson: PackageJson;
|
|
14
|
+
packageJsonOrig: PackageJson;
|
|
15
|
+
workspaceVersions: Record<string, string>;
|
|
16
|
+
buildDirName: string;
|
|
17
|
+
baseDir?: string;
|
|
18
|
+
exclude?: string[];
|
|
19
|
+
}): DenoJson;
|
package/jsr/deno-json.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { asNonNull } from "@fuman/utils";
|
|
2
|
+
function packageJsonToDeno({
|
|
3
|
+
packageJson,
|
|
4
|
+
packageJsonOrig,
|
|
5
|
+
workspaceVersions,
|
|
6
|
+
exclude,
|
|
7
|
+
buildDirName,
|
|
8
|
+
baseDir
|
|
9
|
+
}) {
|
|
10
|
+
const importMap = {};
|
|
11
|
+
const exports = {};
|
|
12
|
+
for (const field of ["dependencies", "peerDependencies", "optionalDependencies"]) {
|
|
13
|
+
const deps = packageJson[field];
|
|
14
|
+
if (deps == null) continue;
|
|
15
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
16
|
+
if (typeof version !== "string") continue;
|
|
17
|
+
if (name in workspaceVersions) {
|
|
18
|
+
continue;
|
|
19
|
+
} else if (version.startsWith("npm:@jsr/")) {
|
|
20
|
+
const jsrName = version.slice(9).split("@")[0].replace("__", "/");
|
|
21
|
+
const jsrVersion = version.slice(9).split("@")[1];
|
|
22
|
+
importMap[name] = `jsr:@${jsrName}@${jsrVersion}`;
|
|
23
|
+
} else if (name) {
|
|
24
|
+
let packageName = name;
|
|
25
|
+
let packageVersion = version;
|
|
26
|
+
if (version.startsWith("npm:")) {
|
|
27
|
+
const idx = packageName.indexOf("@");
|
|
28
|
+
if (idx === -1) {
|
|
29
|
+
throw new Error(`Invalid npm dependency: ${name}`);
|
|
30
|
+
}
|
|
31
|
+
packageVersion = packageName.slice(idx + 1);
|
|
32
|
+
packageName = packageName.slice(4, idx);
|
|
33
|
+
} else if (version.match(/\|\||&&|:/)) {
|
|
34
|
+
throw new Error(`Invalid npm dependency (not supported by JSR): ${name}@${version}`);
|
|
35
|
+
}
|
|
36
|
+
importMap[name] = `npm:${packageName}@${packageVersion}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (packageJsonOrig.exports != null) {
|
|
41
|
+
let tmpExports;
|
|
42
|
+
if (typeof packageJsonOrig.exports === "string") {
|
|
43
|
+
tmpExports = { ".": packageJsonOrig.exports };
|
|
44
|
+
} else if (typeof packageJsonOrig.exports !== "object") {
|
|
45
|
+
throw new TypeError("package.json exports must be an object");
|
|
46
|
+
} else {
|
|
47
|
+
tmpExports = packageJsonOrig.exports;
|
|
48
|
+
}
|
|
49
|
+
for (const [name, value] of Object.entries(tmpExports)) {
|
|
50
|
+
if (typeof value !== "string") {
|
|
51
|
+
throw new TypeError(`package.json exports value must be a string: ${name}`);
|
|
52
|
+
}
|
|
53
|
+
if (value.endsWith(".wasm")) continue;
|
|
54
|
+
if (baseDir != null && baseDir !== ".") {
|
|
55
|
+
if (!value.startsWith(`./${baseDir}`)) {
|
|
56
|
+
throw new Error(`Invalid export value: ${value} (must be inside ./${baseDir})`);
|
|
57
|
+
}
|
|
58
|
+
exports[name] = `./${value.slice(baseDir.length + 3)}`;
|
|
59
|
+
} else {
|
|
60
|
+
exports[name] = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
name: asNonNull(packageJson.name),
|
|
66
|
+
version: asNonNull(packageJson.version),
|
|
67
|
+
exports,
|
|
68
|
+
exclude,
|
|
69
|
+
imports: importMap,
|
|
70
|
+
publish: {
|
|
71
|
+
// in the probable case we have `dist` in .gitignore, deno will ignore it by default
|
|
72
|
+
// but since the dist in our case is the generated JSR package, we do want to include it
|
|
73
|
+
exclude: [`!../${buildDirName}`]
|
|
74
|
+
},
|
|
75
|
+
...packageJson.denoJson != null && typeof packageJson.denoJson === "object" ? packageJson.denoJson : {}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
packageJsonToDeno
|
|
80
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WorkspacePackage } from '../package-json/collect-package-jsons.js';
|
|
2
|
+
import { JsrConfig } from './config.js';
|
|
3
|
+
export declare function generateDenoWorkspace(params: {
|
|
4
|
+
workspaceRoot: string | URL;
|
|
5
|
+
workspacePackages?: WorkspacePackage[];
|
|
6
|
+
rootConfig?: JsrConfig;
|
|
7
|
+
withDryRun?: boolean;
|
|
8
|
+
fixedVersion?: string;
|
|
9
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as fsp from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { asyncPool } from "@fuman/utils";
|
|
5
|
+
import { glob } from "tinyglobby";
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { loadBuildConfig } from "../misc/_config.js";
|
|
8
|
+
import { exec } from "../misc/exec.js";
|
|
9
|
+
import { tryCopy } from "../misc/fs.js";
|
|
10
|
+
import { normalizeFilePath } from "../misc/path.js";
|
|
11
|
+
import { collectPackageJsons, filterPackageJsonsForPublish } from "../package-json/collect-package-jsons.js";
|
|
12
|
+
import { processPackageJson } from "../package-json/process-package-json.js";
|
|
13
|
+
import { findRootPackage, collectVersions } from "../package-json/utils.js";
|
|
14
|
+
import { packageJsonToDeno } from "./deno-json.js";
|
|
15
|
+
function mergeArrays(a, b, defaultValue = []) {
|
|
16
|
+
if (!a) return b ?? defaultValue;
|
|
17
|
+
if (!b) return a;
|
|
18
|
+
return [...a, ...b];
|
|
19
|
+
}
|
|
20
|
+
async function generateDenoWorkspace(params) {
|
|
21
|
+
const {
|
|
22
|
+
workspaceRoot: workspaceRoot_,
|
|
23
|
+
workspacePackages = await collectPackageJsons(workspaceRoot_, true),
|
|
24
|
+
rootConfig,
|
|
25
|
+
withDryRun = false,
|
|
26
|
+
fixedVersion
|
|
27
|
+
} = params;
|
|
28
|
+
const workspaceRoot = normalizeFilePath(workspaceRoot_);
|
|
29
|
+
const rootPackage = findRootPackage(workspacePackages);
|
|
30
|
+
const outDir = join(workspaceRoot, rootConfig?.outputDir ?? "dist");
|
|
31
|
+
await fsp.rm(outDir, { recursive: true, force: true });
|
|
32
|
+
await fsp.mkdir(outDir, { recursive: true });
|
|
33
|
+
const rootDenoJson = {
|
|
34
|
+
workspace: []
|
|
35
|
+
};
|
|
36
|
+
for (const pkg of filterPackageJsonsForPublish(workspacePackages, "jsr")) {
|
|
37
|
+
if (pkg.json.name == null) continue;
|
|
38
|
+
if (rootConfig?.includePackage != null && !rootConfig.includePackage(pkg)) continue;
|
|
39
|
+
const packageRoot = pkg.path;
|
|
40
|
+
const packageDirName = pkg.json.name.replace(/\//g, "__");
|
|
41
|
+
const packageOutRoot = join(outDir, packageDirName);
|
|
42
|
+
rootDenoJson.workspace.push(`./${packageDirName}`);
|
|
43
|
+
await fsp.mkdir(packageOutRoot, { recursive: true });
|
|
44
|
+
if (pkg.json.scripts?.["build:jsr"] != null) {
|
|
45
|
+
await exec([
|
|
46
|
+
"npm",
|
|
47
|
+
"run",
|
|
48
|
+
"build:jsr"
|
|
49
|
+
], {
|
|
50
|
+
env: {
|
|
51
|
+
...process.env,
|
|
52
|
+
FUMAN_BUILD_SRC: packageRoot,
|
|
53
|
+
FUMAN_BUILD_OUT: packageOutRoot
|
|
54
|
+
},
|
|
55
|
+
cwd: packageRoot,
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
throwOnError: true
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const packageConfig = await loadBuildConfig(pkg.path);
|
|
62
|
+
const packageConfigJsr = packageConfig?.jsr;
|
|
63
|
+
const srcDir = join(packageRoot, normalizeFilePath(packageConfigJsr?.sourceDir ?? rootConfig?.sourceDir ?? ""));
|
|
64
|
+
const excludeFiles = mergeArrays(rootConfig?.exclude, packageConfigJsr?.exclude);
|
|
65
|
+
await fsp.cp(srcDir, packageOutRoot, { recursive: true });
|
|
66
|
+
const printer = ts.createPrinter();
|
|
67
|
+
const tsFiles = await glob("**/*.ts", {
|
|
68
|
+
cwd: packageOutRoot,
|
|
69
|
+
ignore: excludeFiles
|
|
70
|
+
});
|
|
71
|
+
await asyncPool(tsFiles, async (filename) => {
|
|
72
|
+
const fullFilePath = join(packageOutRoot, filename);
|
|
73
|
+
let fileContent = await fsp.readFile(fullFilePath, "utf8");
|
|
74
|
+
let changed = false;
|
|
75
|
+
const file = ts.createSourceFile(filename, fileContent, ts.ScriptTarget.ESNext, true);
|
|
76
|
+
let changedTs = false;
|
|
77
|
+
for (const imp of file.statements) {
|
|
78
|
+
if (!ts.isImportDeclaration(imp) && !ts.isExportDeclaration(imp)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!imp.moduleSpecifier || !ts.isStringLiteral(imp.moduleSpecifier)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const mod = imp.moduleSpecifier.text;
|
|
85
|
+
if (mod[0] !== ".") {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (mod.endsWith(".js")) {
|
|
89
|
+
changedTs = true;
|
|
90
|
+
imp.moduleSpecifier = ts.factory.createStringLiteral(
|
|
91
|
+
mod.replace(/\.js$/, ".ts")
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error(`Invalid import specifier: ${mod} at ${join(srcDir, filename)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (rootConfig?.transformAst?.(file)) {
|
|
98
|
+
changedTs = true;
|
|
99
|
+
}
|
|
100
|
+
if (packageConfigJsr?.transformAst?.(file)) {
|
|
101
|
+
changedTs = true;
|
|
102
|
+
}
|
|
103
|
+
if (changedTs) {
|
|
104
|
+
fileContent = printer.printFile(file);
|
|
105
|
+
changed = true;
|
|
106
|
+
}
|
|
107
|
+
if (rootConfig?.transformCode || packageConfigJsr?.transformCode) {
|
|
108
|
+
const origFileContent = fileContent;
|
|
109
|
+
if (rootConfig?.transformCode) {
|
|
110
|
+
fileContent = rootConfig.transformCode(filename, fileContent);
|
|
111
|
+
}
|
|
112
|
+
if (packageConfigJsr?.transformCode) {
|
|
113
|
+
fileContent = packageConfigJsr.transformCode(filename, fileContent);
|
|
114
|
+
}
|
|
115
|
+
if (fileContent !== origFileContent) {
|
|
116
|
+
changed = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (changed) {
|
|
120
|
+
await fsp.writeFile(fullFilePath, fileContent);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const hookContext = {
|
|
124
|
+
outDir: "",
|
|
125
|
+
packageDir: packageOutRoot,
|
|
126
|
+
packageName: pkg.json.name,
|
|
127
|
+
packageJson: pkg.json,
|
|
128
|
+
jsr: true
|
|
129
|
+
};
|
|
130
|
+
packageConfig?.preparePackageJson?.(hookContext);
|
|
131
|
+
const workspaceVersions = collectVersions(workspacePackages);
|
|
132
|
+
const { packageJson, packageJsonOrig } = processPackageJson({
|
|
133
|
+
packageJson: pkg.json,
|
|
134
|
+
rootPackageJson: rootPackage.json,
|
|
135
|
+
workspaceVersions,
|
|
136
|
+
// since there's no bundling, we can't drop any deps.
|
|
137
|
+
// we *could* copy them from node_modules and add to the import map,
|
|
138
|
+
// but maybe sometime later, doesn't seem like a critical feature
|
|
139
|
+
bundledWorkspaceDeps: [],
|
|
140
|
+
rootFieldsToCopy: ["license"]
|
|
141
|
+
});
|
|
142
|
+
if (fixedVersion != null) {
|
|
143
|
+
packageJson.version = fixedVersion;
|
|
144
|
+
packageJsonOrig.version = fixedVersion;
|
|
145
|
+
}
|
|
146
|
+
const denoJson = packageJsonToDeno({
|
|
147
|
+
packageJson,
|
|
148
|
+
packageJsonOrig,
|
|
149
|
+
workspaceVersions,
|
|
150
|
+
buildDirName: relative(packageOutRoot, outDir),
|
|
151
|
+
baseDir: relative(packageRoot, srcDir),
|
|
152
|
+
exclude: excludeFiles
|
|
153
|
+
});
|
|
154
|
+
await fsp.writeFile(join(packageOutRoot, "deno.json"), JSON.stringify(denoJson, null, 4));
|
|
155
|
+
for (const file of mergeArrays(rootConfig?.copyRootFiles, packageConfig?.jsr?.copyRootFiles, ["LICENSE"])) {
|
|
156
|
+
await tryCopy(join(workspaceRoot, file), join(packageOutRoot, file), { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
for (const file of mergeArrays(rootConfig?.copyPackageFiles, packageConfig?.jsr?.copyPackageFiles, ["README.md"])) {
|
|
159
|
+
await tryCopy(join(packageRoot, file), join(packageOutRoot, file), { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
await fsp.writeFile(join(outDir, "deno.json"), JSON.stringify(rootDenoJson, null, 4));
|
|
163
|
+
if (rootConfig?.dryRun !== false || withDryRun) {
|
|
164
|
+
await exec(["deno", "publish", "--dry-run", "-q", "--allow-dirty"], {
|
|
165
|
+
cwd: outDir,
|
|
166
|
+
stdio: "inherit",
|
|
167
|
+
throwOnError: true
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return outDir;
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
generateDenoWorkspace
|
|
174
|
+
};
|
package/jsr/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Populate a local JSR instance with packages from an upstream registry
|
|
3
|
+
*/
|
|
4
|
+
export declare function populateFromUpstream(params: {
|
|
5
|
+
/**
|
|
6
|
+
* URL of the upstream registry
|
|
7
|
+
*
|
|
8
|
+
* @default "https://jsr.io"
|
|
9
|
+
*/
|
|
10
|
+
upstream?: string;
|
|
11
|
+
/**
|
|
12
|
+
* URL of the downstream local registry
|
|
13
|
+
*/
|
|
14
|
+
downstream: string;
|
|
15
|
+
/**
|
|
16
|
+
* List of packages to populate (in format: `@scope/name@version`, e.g. `@std/fs@0.105.0`)
|
|
17
|
+
*/
|
|
18
|
+
packages: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Optional token for authentication (will be passed as `-t` to `deno publish`). Useful for CI/CD.
|
|
21
|
+
*/
|
|
22
|
+
token?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to create packages via the API instead of interactively via web UI. Useful for CI/CD.
|
|
25
|
+
*
|
|
26
|
+
* Requires {@link token} to be set.
|
|
27
|
+
*/
|
|
28
|
+
createViaApi?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Deno executable path
|
|
31
|
+
*
|
|
32
|
+
* @default "deno" (from PATH)
|
|
33
|
+
*/
|
|
34
|
+
deno?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to suppress output
|
|
37
|
+
*
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
quiet?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Additional arguments to pass to `deno publish`
|
|
43
|
+
*/
|
|
44
|
+
publishArgs?: string[] | ((pkg: string) => string[]);
|
|
45
|
+
}): Promise<void>;
|
package/jsr/populate.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as fsp from "node:fs/promises";
|
|
2
|
+
import { join, resolve, dirname } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { AsyncQueue, asyncPool } from "@fuman/utils";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
import { exec } from "../misc/exec.js";
|
|
7
|
+
import { determinePublishOrder } from "../misc/publish-order.js";
|
|
8
|
+
import { parseImportSpecifier } from "./utils/external-libs.js";
|
|
9
|
+
import { jsrMaybeCreatePackage } from "./utils/jsr-api.js";
|
|
10
|
+
import { findClosestJsrJson, parseJsrJson } from "./utils/jsr-json.js";
|
|
11
|
+
import { downloadJsrPackage } from "./utils/jsr.js";
|
|
12
|
+
async function findPackageDependencies(packagePath) {
|
|
13
|
+
const jsrJsonPath = findClosestJsrJson(packagePath);
|
|
14
|
+
if (jsrJsonPath == null) {
|
|
15
|
+
throw new Error(`Could not find jsr.json for package at ${packagePath}`);
|
|
16
|
+
}
|
|
17
|
+
const jsrJson = parseJsrJson(await fsp.readFile(jsrJsonPath, "utf8"));
|
|
18
|
+
const entrypoints = typeof jsrJson.exports === "string" ? [jsrJson.exports] : Object.values(jsrJson.exports);
|
|
19
|
+
const visited = /* @__PURE__ */ new Set();
|
|
20
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
21
|
+
const queue = [...entrypoints];
|
|
22
|
+
while (queue.length > 0) {
|
|
23
|
+
let handleSpecifier = function(specifier) {
|
|
24
|
+
if (specifier.startsWith("jsr:")) {
|
|
25
|
+
const parsed = parseImportSpecifier(specifier);
|
|
26
|
+
dependencies.add(`${parsed.packageName}@${parsed.version}`);
|
|
27
|
+
} else if (specifier.startsWith(".")) {
|
|
28
|
+
const resolved = resolve(dirname(join(packagePath, file)), specifier);
|
|
29
|
+
let relative = resolved.slice(packagePath.length + 1);
|
|
30
|
+
if (!relative.startsWith(".")) relative = `./${relative}`;
|
|
31
|
+
queue.push(relative);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const file = queue.shift();
|
|
35
|
+
if (visited.has(file)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
visited.add(file);
|
|
39
|
+
const content = await fsp.readFile(join(packagePath, file), "utf8");
|
|
40
|
+
const source = ts.createSourceFile(file, content, ts.ScriptTarget.ESNext, true);
|
|
41
|
+
for (const node of source.statements) {
|
|
42
|
+
if (ts.isImportDeclaration(node)) {
|
|
43
|
+
const specifier = node.moduleSpecifier.getText().slice(1, -1);
|
|
44
|
+
handleSpecifier(specifier);
|
|
45
|
+
} else if (ts.isExportDeclaration(node)) {
|
|
46
|
+
if (node.moduleSpecifier) {
|
|
47
|
+
const specifier = node.moduleSpecifier.getText().slice(1, -1);
|
|
48
|
+
handleSpecifier(specifier);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return Array.from(dependencies);
|
|
54
|
+
}
|
|
55
|
+
async function populateFromUpstream(params) {
|
|
56
|
+
const {
|
|
57
|
+
upstream = "https://jsr.io",
|
|
58
|
+
downstream,
|
|
59
|
+
packages,
|
|
60
|
+
token,
|
|
61
|
+
createViaApi = false,
|
|
62
|
+
deno = "deno",
|
|
63
|
+
quiet = false,
|
|
64
|
+
publishArgs: _publishArgs
|
|
65
|
+
} = params;
|
|
66
|
+
const publishArgs = typeof _publishArgs === "function" ? _publishArgs : () => _publishArgs || [];
|
|
67
|
+
if (createViaApi && token == null) {
|
|
68
|
+
throw new Error("createViaApi requires a token");
|
|
69
|
+
}
|
|
70
|
+
const depsMap = /* @__PURE__ */ new Map();
|
|
71
|
+
const nameToPath = /* @__PURE__ */ new Map();
|
|
72
|
+
const downloadQueue = new AsyncQueue(packages);
|
|
73
|
+
let i = 0;
|
|
74
|
+
let total = downloadQueue.length;
|
|
75
|
+
await asyncPool(downloadQueue, async (pkg) => {
|
|
76
|
+
i += 1;
|
|
77
|
+
if (nameToPath.has(pkg)) {
|
|
78
|
+
if (downloadQueue.length === 0) {
|
|
79
|
+
downloadQueue.end();
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!quiet) {
|
|
84
|
+
console.log(`[${i}/${total}] Downloading ${pkg}...`);
|
|
85
|
+
}
|
|
86
|
+
const specifier = parseImportSpecifier(`jsr:${pkg}`);
|
|
87
|
+
const path = await downloadJsrPackage(specifier, { registry: upstream });
|
|
88
|
+
nameToPath.set(pkg, path);
|
|
89
|
+
const deps = await findPackageDependencies(path);
|
|
90
|
+
depsMap.set(pkg, deps);
|
|
91
|
+
deps.forEach((dep) => downloadQueue.enqueue(dep));
|
|
92
|
+
total += deps.length;
|
|
93
|
+
if (downloadQueue.length === 0 && deps.length === 0) {
|
|
94
|
+
downloadQueue.end();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const order = determinePublishOrder(Object.fromEntries(depsMap));
|
|
98
|
+
for (const item of order) {
|
|
99
|
+
const spec = parseImportSpecifier(`jsr:${item}`);
|
|
100
|
+
const path = nameToPath.get(item);
|
|
101
|
+
if (createViaApi) {
|
|
102
|
+
await jsrMaybeCreatePackage({
|
|
103
|
+
name: spec.packageName,
|
|
104
|
+
registry: downstream,
|
|
105
|
+
// eslint-disable-next-line ts/no-non-null-assertion
|
|
106
|
+
token,
|
|
107
|
+
quiet
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (!quiet) {
|
|
111
|
+
console.log(`Publishing ${item}...`);
|
|
112
|
+
}
|
|
113
|
+
await exec([
|
|
114
|
+
deno,
|
|
115
|
+
"publish",
|
|
116
|
+
"--quiet",
|
|
117
|
+
...token != null ? ["--token", token] : [],
|
|
118
|
+
...publishArgs(item)
|
|
119
|
+
], {
|
|
120
|
+
env: {
|
|
121
|
+
...process.env,
|
|
122
|
+
JSR_URL: downstream
|
|
123
|
+
},
|
|
124
|
+
cwd: path,
|
|
125
|
+
stdio: "inherit",
|
|
126
|
+
throwOnError: true
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
populateFromUpstream
|
|
132
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function getModuleCacheDirectory(): string;
|
|
2
|
+
export interface ImportSpecifier {
|
|
3
|
+
registry: 'npm' | 'jsr';
|
|
4
|
+
packageName: string;
|
|
5
|
+
version: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseImportSpecifier(importSpecifier: string): ImportSpecifier;
|
|
8
|
+
export declare function splitImportRequest(request: string): [string, string];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
let _cacheDir;
|
|
4
|
+
function getModuleCacheDirectory() {
|
|
5
|
+
if (process.env.JSR_CACHE_DIR != null) return process.env.JSR_CACHE_DIR;
|
|
6
|
+
if (_cacheDir != null) return _cacheDir;
|
|
7
|
+
switch (process.platform) {
|
|
8
|
+
case "win32": {
|
|
9
|
+
return _cacheDir = join(process.env.LOCALAPPDATA ?? process.env.APPDATA ?? "C:", "jsr-cache");
|
|
10
|
+
}
|
|
11
|
+
case "darwin": {
|
|
12
|
+
return _cacheDir = join(process.env.HOME ?? "/tmp", "Library", "Caches", "jsr");
|
|
13
|
+
}
|
|
14
|
+
default: {
|
|
15
|
+
return _cacheDir = join(process.env.XDG_CACHE_HOME ?? process.env.HOME ?? "/tmp", ".cache", "jsr");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function parseImportSpecifier(importSpecifier) {
|
|
20
|
+
let [registry, specifier] = importSpecifier.split(":");
|
|
21
|
+
if (registry !== "npm" && registry !== "jsr") {
|
|
22
|
+
throw new Error(`Invalid import specifier: ${importSpecifier}`);
|
|
23
|
+
}
|
|
24
|
+
if (registry === "jsr" && specifier[0] === "/") {
|
|
25
|
+
specifier = specifier.slice(1);
|
|
26
|
+
}
|
|
27
|
+
if (specifier.startsWith("@")) {
|
|
28
|
+
const [pkg2, version2] = specifier.slice(1).split("@");
|
|
29
|
+
return { registry, packageName: `@${pkg2}`, version: version2.split("/")[0] };
|
|
30
|
+
}
|
|
31
|
+
const [pkg, version] = specifier.split("@");
|
|
32
|
+
return { registry, packageName: pkg, version: version.split("/")[0] };
|
|
33
|
+
}
|
|
34
|
+
function splitImportRequest(request) {
|
|
35
|
+
if (request.startsWith("jsr:/")) request = `jsr:${request.slice(5)}`;
|
|
36
|
+
const parts = request.split("/");
|
|
37
|
+
if (parts[0].match(/^(?:npm:|jsr:)?@/)) {
|
|
38
|
+
return [parts.slice(0, 2).join("/"), parts.slice(2).join("/")];
|
|
39
|
+
}
|
|
40
|
+
return [parts[0], parts.slice(1).join("/")];
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
getModuleCacheDirectory,
|
|
44
|
+
parseImportSpecifier,
|
|
45
|
+
splitImportRequest
|
|
46
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare function jsrGetScopeInfo(params: {
|
|
2
|
+
scope: string;
|
|
3
|
+
registry: string;
|
|
4
|
+
}): Promise<any>;
|
|
5
|
+
export declare function jsrCreateScope(params: {
|
|
6
|
+
name: string;
|
|
7
|
+
registry: string;
|
|
8
|
+
token: string;
|
|
9
|
+
quiet?: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function jsrMaybeCreatePackage(params: {
|
|
12
|
+
name: string;
|
|
13
|
+
registry: string;
|
|
14
|
+
token: string;
|
|
15
|
+
quiet?: boolean;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
export declare function jsrSetGithubRepo(params: {
|
|
18
|
+
registry: string;
|
|
19
|
+
name: string;
|
|
20
|
+
token: string;
|
|
21
|
+
owner: string;
|
|
22
|
+
repo: string;
|
|
23
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createFfetch, ffetchAddons } from "@fuman/fetch";
|
|
2
|
+
const ffetch = /* @__PURE__ */ createFfetch({
|
|
3
|
+
addons: [
|
|
4
|
+
/* @__PURE__ */ ffetchAddons.timeout(),
|
|
5
|
+
/* @__PURE__ */ ffetchAddons.retry()
|
|
6
|
+
],
|
|
7
|
+
retry: { maxRetries: 3 },
|
|
8
|
+
timeout: 5e3
|
|
9
|
+
});
|
|
10
|
+
async function jsrGetScopeInfo(params) {
|
|
11
|
+
const { scope, registry } = params;
|
|
12
|
+
const res = await ffetch(`/api/scopes/${scope}`, {
|
|
13
|
+
baseUrl: registry,
|
|
14
|
+
validateResponse: false
|
|
15
|
+
});
|
|
16
|
+
if (res.status === 404) return null;
|
|
17
|
+
if (res.status !== 200) {
|
|
18
|
+
throw new Error(`Failed to get scope info: ${res.statusText}`);
|
|
19
|
+
}
|
|
20
|
+
return res.json();
|
|
21
|
+
}
|
|
22
|
+
async function jsrCreateScope(params) {
|
|
23
|
+
const { name, registry, token, quiet } = params;
|
|
24
|
+
const create = await ffetch("/api/scopes", {
|
|
25
|
+
baseUrl: registry,
|
|
26
|
+
headers: {
|
|
27
|
+
Cookie: `token=${token}`
|
|
28
|
+
},
|
|
29
|
+
json: { scope: name }
|
|
30
|
+
});
|
|
31
|
+
if (create.status !== 200) {
|
|
32
|
+
throw new Error(`Failed to create scope: ${create.statusText} ${await create.text()}`);
|
|
33
|
+
}
|
|
34
|
+
if (!quiet) {
|
|
35
|
+
console.log("Created scope @%s", name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function jsrMaybeCreatePackage(params) {
|
|
39
|
+
const { name, registry, token, quiet } = params;
|
|
40
|
+
const [scopeWithAt, packageName] = name.split("/");
|
|
41
|
+
if (!packageName || !scopeWithAt || !scopeWithAt.startsWith("@")) {
|
|
42
|
+
throw new Error("Invalid package name");
|
|
43
|
+
}
|
|
44
|
+
const scope = scopeWithAt.slice(1);
|
|
45
|
+
const packageMeta = await ffetch(`/api/scopes/${scope}/packages/${packageName}`, {
|
|
46
|
+
baseUrl: registry,
|
|
47
|
+
validateResponse: false
|
|
48
|
+
});
|
|
49
|
+
if (packageMeta.status === 200) return;
|
|
50
|
+
if (packageMeta.status !== 404) {
|
|
51
|
+
throw new Error(`Failed to check package: ${packageMeta.statusText} ${await packageMeta.text()}`);
|
|
52
|
+
}
|
|
53
|
+
if (!quiet) {
|
|
54
|
+
console.log("%s does not exist, creating..", name);
|
|
55
|
+
}
|
|
56
|
+
const create = await ffetch(`/api/scopes/${scope}/packages`, {
|
|
57
|
+
baseUrl: registry,
|
|
58
|
+
headers: {
|
|
59
|
+
Cookie: `token=${token}`
|
|
60
|
+
},
|
|
61
|
+
json: { package: packageName },
|
|
62
|
+
validateResponse: false
|
|
63
|
+
});
|
|
64
|
+
if (create.status !== 200) {
|
|
65
|
+
const text = await create.text();
|
|
66
|
+
if (create.status === 403) {
|
|
67
|
+
const json = JSON.parse(text);
|
|
68
|
+
if (json.code === "actorNotScopeMember") {
|
|
69
|
+
const info = await jsrGetScopeInfo({ scope, registry });
|
|
70
|
+
if (info === null) {
|
|
71
|
+
await jsrCreateScope({ name: scope, registry, token, quiet });
|
|
72
|
+
return jsrMaybeCreatePackage({
|
|
73
|
+
...params,
|
|
74
|
+
quiet: true
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`Failed to create package: ${create.statusText} ${text}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function jsrSetGithubRepo(params) {
|
|
83
|
+
const {
|
|
84
|
+
registry,
|
|
85
|
+
name,
|
|
86
|
+
token,
|
|
87
|
+
owner,
|
|
88
|
+
repo
|
|
89
|
+
} = params;
|
|
90
|
+
const [scopeWithAt, packageName] = name.split("/");
|
|
91
|
+
if (!packageName || !scopeWithAt || !scopeWithAt.startsWith("@")) {
|
|
92
|
+
throw new Error("Invalid package name");
|
|
93
|
+
}
|
|
94
|
+
const scope = scopeWithAt.slice(1);
|
|
95
|
+
const res = await ffetch(`/api/scopes/${scope}/packages/${packageName}`, {
|
|
96
|
+
method: "PATCH",
|
|
97
|
+
baseUrl: registry,
|
|
98
|
+
validateResponse: false,
|
|
99
|
+
headers: {
|
|
100
|
+
Cookie: `token=${token}`
|
|
101
|
+
},
|
|
102
|
+
json: {
|
|
103
|
+
githubRepository: { owner, name: repo }
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
if (res.status !== 200) {
|
|
107
|
+
throw new Error(`Failed to set github repo: ${await res.text()}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
jsrCreateScope,
|
|
112
|
+
jsrGetScopeInfo,
|
|
113
|
+
jsrMaybeCreatePackage,
|
|
114
|
+
jsrSetGithubRepo
|
|
115
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ImportSpecifier } from './external-libs.js';
|
|
2
|
+
export interface JsrJson {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
exports: string | Record<string, string>;
|
|
6
|
+
imports?: Record<string, ImportSpecifier>;
|
|
7
|
+
compilerOptions?: object;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseJsrJson(json: string): JsrJson;
|
|
10
|
+
export declare function findClosestJsrJson(path: string): string | null;
|