@shazhou/proman-core 0.9.0
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/CHANGELOG.md +26 -0
- package/LICENSE +18 -0
- package/dist/commands/bump.d.ts +13 -0
- package/dist/commands/bump.d.ts.map +1 -0
- package/dist/commands/bump.js +115 -0
- package/dist/commands/deploy.d.ts +9 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +42 -0
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +175 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +262 -0
- package/dist/commands/link.d.ts +19 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +155 -0
- package/dist/commands/publish.d.ts +18 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +125 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/load-config.d.ts +6 -0
- package/dist/config/load-config.d.ts.map +1 -0
- package/dist/config/load-config.js +29 -0
- package/dist/config/types.d.ts +17 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +1 -0
- package/dist/config/validate-config.d.ts +7 -0
- package/dist/config/validate-config.d.ts.map +1 -0
- package/dist/config/validate-config.js +72 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/utils/changeset.d.ts +16 -0
- package/dist/utils/changeset.d.ts.map +1 -0
- package/dist/utils/changeset.js +80 -0
- package/dist/utils/fingerprint.d.ts +38 -0
- package/dist/utils/fingerprint.d.ts.map +1 -0
- package/dist/utils/fingerprint.js +182 -0
- package/dist/utils/git.d.ts +23 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +105 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/npm.d.ts +30 -0
- package/dist/utils/npm.d.ts.map +1 -0
- package/dist/utils/npm.js +85 -0
- package/dist/utils/smoke-test.d.ts +7 -0
- package/dist/utils/smoke-test.d.ts.map +1 -0
- package/dist/utils/smoke-test.js +59 -0
- package/dist/utils/version.d.ts +5 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +36 -0
- package/dist/utils/workspace.d.ts +21 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +73 -0
- package/package.json +45 -0
- package/src/commands/bump.ts +131 -0
- package/src/commands/deploy.ts +52 -0
- package/src/commands/dev.ts +214 -0
- package/src/commands/index.ts +7 -0
- package/src/commands/init.integration.test.ts +59 -0
- package/src/commands/init.test.ts +179 -0
- package/src/commands/init.ts +290 -0
- package/src/commands/link.ts +195 -0
- package/src/commands/publish.ts +168 -0
- package/src/config/index.ts +8 -0
- package/src/config/load-config.ts +33 -0
- package/src/config/types.ts +19 -0
- package/src/config/validate-config.ts +81 -0
- package/src/index.ts +29 -0
- package/src/utils/changeset.ts +98 -0
- package/src/utils/fingerprint.ts +199 -0
- package/src/utils/git.ts +119 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/npm.ts +110 -0
- package/src/utils/smoke-test.ts +79 -0
- package/src/utils/version.ts +41 -0
- package/src/utils/workspace.ts +94 -0
- package/tests/build-fingerprint-integration.test.ts +403 -0
- package/tests/bump.test.ts +261 -0
- package/tests/changeset.test.ts +147 -0
- package/tests/deploy.test.ts +98 -0
- package/tests/dev.test.ts +756 -0
- package/tests/fingerprint.test.ts +316 -0
- package/tests/fixtures/api-only/packages/api/.gitkeep +0 -0
- package/tests/fixtures/api-only/proman.yaml +4 -0
- package/tests/fixtures/bad-packages/proman.yaml +1 -0
- package/tests/fixtures/bun-project/packages/a/.gitkeep +0 -0
- package/tests/fixtures/bun-project/proman.yaml +4 -0
- package/tests/fixtures/defaults/proman.yaml +3 -0
- package/tests/fixtures/no-deployable/packages/core/.gitkeep +0 -0
- package/tests/fixtures/no-deployable/packages/mycli/.gitkeep +0 -0
- package/tests/fixtures/no-deployable/proman.yaml +7 -0
- package/tests/fixtures/node-runtime/packages/a/package.json +5 -0
- package/tests/fixtures/node-runtime/proman.yaml +3 -0
- package/tests/fixtures/pnpm-project/packages/a/package.json +1 -0
- package/tests/fixtures/pnpm-project/pnpm-lock.yaml +0 -0
- package/tests/fixtures/pnpm-project/proman.yaml +3 -0
- package/tests/fixtures/typed/packages/api/.gitkeep +0 -0
- package/tests/fixtures/typed/packages/core/.gitkeep +0 -0
- package/tests/fixtures/typed/packages/dashboard/.gitkeep +0 -0
- package/tests/fixtures/typed/packages/mycli/.gitkeep +0 -0
- package/tests/fixtures/typed/proman.yaml +13 -0
- package/tests/fixtures/valid/packages/cli/package.json +5 -0
- package/tests/fixtures/valid/packages/core/package.json +5 -0
- package/tests/fixtures/valid/packages/fs/package.json +5 -0
- package/tests/fixtures/valid/proman.yaml +13 -0
- package/tests/fixtures/webui-only/packages/dashboard/.gitkeep +0 -0
- package/tests/fixtures/webui-only/proman.yaml +4 -0
- package/tests/link.test.ts +419 -0
- package/tests/load-config.test.ts +44 -0
- package/tests/npm.test.ts +199 -0
- package/tests/publish.test.ts +599 -0
- package/tests/smoke-test.test.ts +211 -0
- package/tests/validate-config.test.ts +67 -0
- package/tests/version.test.ts +86 -0
- package/tests/workflow-schema.test.ts +72 -0
- package/tests/workspace.test.ts +160 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const RELEASE_BRANCH_RE = /^release\/(.+)$/;
|
|
2
|
+
export function parseReleaseBranch(branch) {
|
|
3
|
+
const m = branch.match(RELEASE_BRANCH_RE);
|
|
4
|
+
if (!m)
|
|
5
|
+
throw new Error(`not a release branch: '${branch}'`);
|
|
6
|
+
const v = m[1].trim();
|
|
7
|
+
if (!v)
|
|
8
|
+
throw new Error(`malformed release branch: '${branch}'`);
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
export function nextRcNumber(opts) {
|
|
12
|
+
const { baseVersion, existing } = opts;
|
|
13
|
+
const prefix = `${baseVersion}-rc.`;
|
|
14
|
+
let max = 0;
|
|
15
|
+
for (const v of existing) {
|
|
16
|
+
if (!v.startsWith(prefix))
|
|
17
|
+
continue;
|
|
18
|
+
const tail = v.slice(prefix.length);
|
|
19
|
+
const n = Number.parseInt(tail, 10);
|
|
20
|
+
if (Number.isFinite(n) && n > max)
|
|
21
|
+
max = n;
|
|
22
|
+
}
|
|
23
|
+
return max + 1;
|
|
24
|
+
}
|
|
25
|
+
export function formatRcVersion(baseVersion, n) {
|
|
26
|
+
return `${baseVersion}-rc.${n}`;
|
|
27
|
+
}
|
|
28
|
+
export const defaultRegistryFetch = async (pkg) => {
|
|
29
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg}`);
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
if (res.status === 404)
|
|
32
|
+
return [];
|
|
33
|
+
throw new Error(`registry fetch failed for ${pkg}: ${res.status} ${res.statusText}`);
|
|
34
|
+
}
|
|
35
|
+
const json = (await res.json());
|
|
36
|
+
return Object.keys(json.versions ?? {});
|
|
37
|
+
};
|
|
38
|
+
export const defaultSpawn = async (argv, cwd) => {
|
|
39
|
+
const { spawnSync } = await import('node:child_process');
|
|
40
|
+
const result = spawnSync(argv[0], argv.slice(1), {
|
|
41
|
+
cwd,
|
|
42
|
+
stdio: 'pipe',
|
|
43
|
+
});
|
|
44
|
+
const stdout = result.stdout?.toString() ?? '';
|
|
45
|
+
const stderr = result.stderr?.toString() ?? '';
|
|
46
|
+
// Forward captured output to terminal so users still see progress
|
|
47
|
+
if (stdout)
|
|
48
|
+
process.stdout.write(stdout);
|
|
49
|
+
if (stderr)
|
|
50
|
+
process.stderr.write(stderr);
|
|
51
|
+
return {
|
|
52
|
+
code: result.status ?? 1,
|
|
53
|
+
stdout,
|
|
54
|
+
stderr,
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export async function runOrThrow(spawn, argv, cwd) {
|
|
58
|
+
const { code, stdout, stderr } = await spawn(argv, cwd);
|
|
59
|
+
if (code !== 0) {
|
|
60
|
+
const detail = stderr.trim() || stdout.trim();
|
|
61
|
+
throw new Error(detail ? `${argv.join(' ')} failed: ${detail}` : `${argv.join(' ')} failed`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function createNpmRunner(cwd, spawn = defaultSpawn) {
|
|
65
|
+
const runScript = (script) => async () => {
|
|
66
|
+
await runOrThrow(spawn, ['pnpm', 'run', script], cwd);
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
install: async () => {
|
|
70
|
+
await runOrThrow(spawn, ['pnpm', 'install'], cwd);
|
|
71
|
+
},
|
|
72
|
+
build: runScript('build'),
|
|
73
|
+
test: async () => {
|
|
74
|
+
await runOrThrow(spawn, ['pnpm', 'exec', 'vitest', 'run'], cwd);
|
|
75
|
+
},
|
|
76
|
+
check: runScript('check'),
|
|
77
|
+
format: runScript('format'),
|
|
78
|
+
publish: async (pkgDir, opts) => {
|
|
79
|
+
const args = ['pnpm', 'publish', '--tag', opts.tag, '--no-git-checks'];
|
|
80
|
+
if (opts.access)
|
|
81
|
+
args.push('--access', opts.access);
|
|
82
|
+
await runOrThrow(spawn, args, pkgDir);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SpawnFn } from './npm.js';
|
|
2
|
+
/**
|
|
3
|
+
* Smoke test a package tarball by extracting it and running bin commands.
|
|
4
|
+
* Validates that the packaged artifact actually works before publishing.
|
|
5
|
+
*/
|
|
6
|
+
export declare function smokeTestTarball(pkgDir: string, spawn: SpawnFn): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=smoke-test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke-test.d.ts","sourceRoot":"","sources":["../../src/utils/smoke-test.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAQvC;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DpF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Smoke test a package tarball by extracting it and running bin commands.
|
|
6
|
+
* Validates that the packaged artifact actually works before publishing.
|
|
7
|
+
*/
|
|
8
|
+
export async function smokeTestTarball(pkgDir, spawn) {
|
|
9
|
+
// Read package.json to check for bin entries
|
|
10
|
+
const pkgJsonPath = join(pkgDir, 'package.json');
|
|
11
|
+
const pkgJsonText = await readFile(pkgJsonPath, 'utf8');
|
|
12
|
+
const pkgJson = JSON.parse(pkgJsonText);
|
|
13
|
+
// Skip if no bin entry
|
|
14
|
+
if (!pkgJson.bin) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Normalize bin to Record format
|
|
18
|
+
const binEntries = typeof pkgJson.bin === 'string' ? { [pkgJson.name]: pkgJson.bin } : pkgJson.bin;
|
|
19
|
+
// Skip if bin is empty
|
|
20
|
+
if (Object.keys(binEntries).length === 0) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Step 1: Create tarball with pnpm pack
|
|
24
|
+
const packResult = await spawn(['pnpm', 'pack'], pkgDir);
|
|
25
|
+
if (packResult.code !== 0) {
|
|
26
|
+
throw new Error(`pnpm pack failed: ${packResult.stderr || packResult.stdout}`);
|
|
27
|
+
}
|
|
28
|
+
const tarballName = packResult.stdout.trim();
|
|
29
|
+
if (!tarballName) {
|
|
30
|
+
throw new Error('pnpm pack did not return tarball filename');
|
|
31
|
+
}
|
|
32
|
+
// Step 2: Extract tarball to temp directory
|
|
33
|
+
const { mkdtemp } = await import('node:fs/promises');
|
|
34
|
+
const testDir = await mkdtemp(join(tmpdir(), 'proman-smoke-'));
|
|
35
|
+
try {
|
|
36
|
+
// Extract tarball
|
|
37
|
+
const tarballPath = join(pkgDir, tarballName);
|
|
38
|
+
const extractResult = await spawn(['tar', '-xzf', tarballPath, '-C', testDir], pkgDir);
|
|
39
|
+
if (extractResult.code !== 0) {
|
|
40
|
+
throw new Error(`tar extract failed: ${extractResult.stderr}`);
|
|
41
|
+
}
|
|
42
|
+
// pnpm pack creates a 'package/' directory inside the tarball
|
|
43
|
+
const extractedPkgDir = join(testDir, 'package');
|
|
44
|
+
// Step 3: Test each bin entry
|
|
45
|
+
for (const [binName, binPath] of Object.entries(binEntries)) {
|
|
46
|
+
const binFullPath = join(extractedPkgDir, binPath);
|
|
47
|
+
const binTestResult = await spawn(['node', binFullPath, '--version'], extractedPkgDir);
|
|
48
|
+
if (binTestResult.code !== 0) {
|
|
49
|
+
const errorMsg = binTestResult.stderr.trim() || binTestResult.stdout.trim();
|
|
50
|
+
throw new Error(`smoke test failed for bin '${binName}': ${errorMsg || 'non-zero exit code'}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
// Step 4: Always clean up temp directory and tarball
|
|
56
|
+
await rm(testDir, { recursive: true, force: true });
|
|
57
|
+
await rm(join(pkgDir, tarballName), { force: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Bump, Changeset } from './changeset.js';
|
|
2
|
+
export declare function bumpVersion(current: string, bump: Bump): string;
|
|
3
|
+
export declare function inferBump(changesets: Changeset[]): Record<string, Bump>;
|
|
4
|
+
export declare function parseTagVersion(tag: string): string;
|
|
5
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAIrD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAW/D;AAID,wBAAgB,SAAS,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAWvE;AAID,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMnD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const VERSION_CORE_RE = /^(\d+)\.(\d+)\.(\d+)(?:-[\w.+-]+)?$/;
|
|
2
|
+
export function bumpVersion(current, bump) {
|
|
3
|
+
const m = current.match(VERSION_CORE_RE);
|
|
4
|
+
if (!m) {
|
|
5
|
+
throw new Error(`invalid version: '${current}'`);
|
|
6
|
+
}
|
|
7
|
+
const major = Number(m[1]);
|
|
8
|
+
const minor = Number(m[2]);
|
|
9
|
+
const patch = Number(m[3]);
|
|
10
|
+
if (bump === 'major')
|
|
11
|
+
return `${major + 1}.0.0`;
|
|
12
|
+
if (bump === 'minor')
|
|
13
|
+
return `${major}.${minor + 1}.0`;
|
|
14
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
15
|
+
}
|
|
16
|
+
const ORDER = { patch: 1, minor: 2, major: 3 };
|
|
17
|
+
export function inferBump(changesets) {
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const c of changesets) {
|
|
20
|
+
for (const [pkg, bump] of Object.entries(c.packages)) {
|
|
21
|
+
const current = result[pkg];
|
|
22
|
+
if (current === undefined || ORDER[bump] > ORDER[current]) {
|
|
23
|
+
result[pkg] = bump;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
const TAG_RE = /^v?(\d+\.\d+\.\d+(?:-[\w.+-]+)?)$/;
|
|
30
|
+
export function parseTagVersion(tag) {
|
|
31
|
+
const m = tag.match(TAG_RE);
|
|
32
|
+
if (!m) {
|
|
33
|
+
throw new Error(`invalid tag: '${tag}'`);
|
|
34
|
+
}
|
|
35
|
+
return m[1];
|
|
36
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type PkgManifest = {
|
|
2
|
+
name: string;
|
|
3
|
+
version: string;
|
|
4
|
+
dependencies?: Record<string, string>;
|
|
5
|
+
devDependencies?: Record<string, string>;
|
|
6
|
+
[k: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
type Unresolved = {
|
|
9
|
+
pkg: string;
|
|
10
|
+
dep: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function rewriteWorkspaceDeps(manifests: PkgManifest[]): {
|
|
13
|
+
rewritten: PkgManifest[];
|
|
14
|
+
unresolved: Unresolved[];
|
|
15
|
+
};
|
|
16
|
+
export declare function applyWorkspaceRewrites(rootDir: string, packages: {
|
|
17
|
+
name: string;
|
|
18
|
+
path: string;
|
|
19
|
+
}[]): Promise<string[]>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/utils/workspace.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACrB,CAAA;AAED,KAAK,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AA8B9C,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG;IAC9D,SAAS,EAAE,WAAW,EAAE,CAAA;IACxB,UAAU,EAAE,UAAU,EAAE,CAAA;CACzB,CAiBA;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GACzC,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BnB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
function rewriteDepsField(pkgName, field, versions, unresolved) {
|
|
4
|
+
if (!field)
|
|
5
|
+
return undefined;
|
|
6
|
+
const out = {};
|
|
7
|
+
let changed = false;
|
|
8
|
+
for (const [dep, val] of Object.entries(field)) {
|
|
9
|
+
if (val === 'workspace:*') {
|
|
10
|
+
const v = versions.get(dep);
|
|
11
|
+
if (v) {
|
|
12
|
+
out[dep] = v;
|
|
13
|
+
changed = true;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
out[dep] = val;
|
|
17
|
+
unresolved.push({ pkg: pkgName, dep });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
out[dep] = val;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Mark via reference identity if changed; caller can compare to original
|
|
25
|
+
void changed;
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
export function rewriteWorkspaceDeps(manifests) {
|
|
29
|
+
const versions = new Map();
|
|
30
|
+
for (const m of manifests) {
|
|
31
|
+
versions.set(m.name, m.version);
|
|
32
|
+
}
|
|
33
|
+
const unresolved = [];
|
|
34
|
+
const rewritten = manifests.map((m) => {
|
|
35
|
+
const copy = { ...m };
|
|
36
|
+
if (m.dependencies) {
|
|
37
|
+
copy.dependencies = rewriteDepsField(m.name, m.dependencies, versions, unresolved);
|
|
38
|
+
}
|
|
39
|
+
if (m.devDependencies) {
|
|
40
|
+
copy.devDependencies = rewriteDepsField(m.name, m.devDependencies, versions, unresolved);
|
|
41
|
+
}
|
|
42
|
+
return copy;
|
|
43
|
+
});
|
|
44
|
+
return { rewritten, unresolved };
|
|
45
|
+
}
|
|
46
|
+
export async function applyWorkspaceRewrites(rootDir, packages) {
|
|
47
|
+
const paths = packages.map((p) => resolve(rootDir, p.path, 'package.json'));
|
|
48
|
+
const manifests = [];
|
|
49
|
+
for (let i = 0; i < paths.length; i++) {
|
|
50
|
+
const p = paths[i];
|
|
51
|
+
let text;
|
|
52
|
+
try {
|
|
53
|
+
text = await readFile(p, 'utf8');
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw new Error(`package.json not found at ${p}: ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
manifests.push(JSON.parse(text));
|
|
59
|
+
}
|
|
60
|
+
const { rewritten } = rewriteWorkspaceDeps(manifests);
|
|
61
|
+
const changed = [];
|
|
62
|
+
for (let i = 0; i < paths.length; i++) {
|
|
63
|
+
const before = JSON.stringify(manifests[i]);
|
|
64
|
+
const after = JSON.stringify(rewritten[i]);
|
|
65
|
+
if (before !== after) {
|
|
66
|
+
const p = paths[i];
|
|
67
|
+
const newText = `${JSON.stringify(rewritten[i], null, 2)}\n`;
|
|
68
|
+
await writeFile(p, newText);
|
|
69
|
+
changed.push(p);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return changed;
|
|
73
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shazhou/proman-core",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Core library for TypeScript monorepo management",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"typescript",
|
|
8
|
+
"monorepo",
|
|
9
|
+
"build",
|
|
10
|
+
"library"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./commands": "./dist/commands/index.js",
|
|
15
|
+
"./utils": "./dist/utils/index.js",
|
|
16
|
+
"./config": "./dist/config/index.js"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.0.0",
|
|
20
|
+
"typescript": "^5.0.0",
|
|
21
|
+
"vitest": "^4.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"yaml": "^2.9.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@biomejs/biome": "^2.0.0",
|
|
28
|
+
"typescript": "^5.0.0",
|
|
29
|
+
"vite": "^6.0.0",
|
|
30
|
+
"vitest": "^4.0.0",
|
|
31
|
+
"wrangler": "^3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"vite": {
|
|
35
|
+
"optional": true
|
|
36
|
+
},
|
|
37
|
+
"wrangler": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "vitest run --passWithNoTests",
|
|
43
|
+
"build": "tsc --project tsconfig.build.json"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readFile, stat, unlink, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { loadConfig } from '../config/load-config.js'
|
|
4
|
+
import {
|
|
5
|
+
buildChangelogEntry,
|
|
6
|
+
type Changeset,
|
|
7
|
+
prependChangelog,
|
|
8
|
+
readChangesets,
|
|
9
|
+
} from '../utils/changeset.js'
|
|
10
|
+
import { bumpVersion, inferBump } from '../utils/version.js'
|
|
11
|
+
|
|
12
|
+
export type BumpOptions = {
|
|
13
|
+
type?: 'major' | 'minor' | 'patch'
|
|
14
|
+
cwd?: string
|
|
15
|
+
now?: () => Date
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function readJson(path: string): Promise<Record<string, unknown>> {
|
|
19
|
+
const text = await readFile(path, 'utf8')
|
|
20
|
+
return JSON.parse(text) as Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function writeJson(path: string, data: Record<string, unknown>): Promise<void> {
|
|
24
|
+
await writeFile(path, `${JSON.stringify(data, null, 2)}\n`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatDate(d: Date): string {
|
|
28
|
+
const y = d.getUTCFullYear()
|
|
29
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, '0')
|
|
30
|
+
const day = String(d.getUTCDate()).padStart(2, '0')
|
|
31
|
+
return `${y}-${m}-${day}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
35
|
+
try {
|
|
36
|
+
await stat(path)
|
|
37
|
+
return true
|
|
38
|
+
} catch {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Bump package versions independently.
|
|
45
|
+
* --type: bump all packages with the given type.
|
|
46
|
+
* No --type: infer per-package bumps from changesets; only bump packages mentioned in changesets.
|
|
47
|
+
* Returns a map of package name → new version.
|
|
48
|
+
*/
|
|
49
|
+
export async function bump(opts: BumpOptions = {}): Promise<Record<string, string>> {
|
|
50
|
+
const cwd = opts.cwd ?? process.cwd()
|
|
51
|
+
const now = opts.now ?? (() => new Date())
|
|
52
|
+
const cfg = loadConfig(cwd)
|
|
53
|
+
|
|
54
|
+
const bumped: Record<string, string> = {}
|
|
55
|
+
|
|
56
|
+
if (opts.type) {
|
|
57
|
+
// Explicit --type: bump all packages
|
|
58
|
+
for (const pkg of cfg.packages) {
|
|
59
|
+
const pkgPath = resolve(cwd, pkg.path, 'package.json')
|
|
60
|
+
const json = await readJson(pkgPath)
|
|
61
|
+
const current = json.version as string
|
|
62
|
+
if (!current) throw new Error(`missing version in ${pkgPath}`)
|
|
63
|
+
const version = bumpVersion(current, opts.type)
|
|
64
|
+
json.version = version
|
|
65
|
+
await writeJson(pkgPath, json)
|
|
66
|
+
bumped[pkg.name] = version
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// Infer from changesets: per-package independent bump
|
|
70
|
+
const changesets = await readChangesets(cwd)
|
|
71
|
+
if (changesets.length === 0) {
|
|
72
|
+
throw new Error('no --type specified and no pending changesets found')
|
|
73
|
+
}
|
|
74
|
+
const bumpMap = inferBump(changesets)
|
|
75
|
+
if (Object.keys(bumpMap).length === 0) {
|
|
76
|
+
throw new Error('no inferable bump from changeset entries')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const pkgByName = new Map(cfg.packages.map((p) => [p.name, p]))
|
|
80
|
+
|
|
81
|
+
for (const [pkgName, bumpType] of Object.entries(bumpMap)) {
|
|
82
|
+
const pkg = pkgByName.get(pkgName)
|
|
83
|
+
if (!pkg) continue // changeset mentions unknown package, skip
|
|
84
|
+
const pkgPath = resolve(cwd, pkg.path, 'package.json')
|
|
85
|
+
const json = await readJson(pkgPath)
|
|
86
|
+
const current = json.version as string
|
|
87
|
+
if (!current) throw new Error(`missing version in ${pkgPath}`)
|
|
88
|
+
const version = bumpVersion(current, bumpType)
|
|
89
|
+
json.version = version
|
|
90
|
+
await writeJson(pkgPath, json)
|
|
91
|
+
bumped[pkgName] = version
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Generate CHANGELOG.md per bumped package
|
|
95
|
+
const date = formatDate(now())
|
|
96
|
+
const byPackage: Record<string, Changeset[]> = {}
|
|
97
|
+
for (const cs of changesets) {
|
|
98
|
+
for (const pkg of Object.keys(cs.packages)) {
|
|
99
|
+
if (!pkgByName.has(pkg)) continue
|
|
100
|
+
const arr = byPackage[pkg] ?? []
|
|
101
|
+
arr.push(cs)
|
|
102
|
+
byPackage[pkg] = arr
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const [pkgName, version] of Object.entries(bumped)) {
|
|
107
|
+
const list = byPackage[pkgName]
|
|
108
|
+
if (!list || list.length === 0) continue
|
|
109
|
+
const pkg = pkgByName.get(pkgName)
|
|
110
|
+
if (!pkg) continue
|
|
111
|
+
const entry = buildChangelogEntry({
|
|
112
|
+
version,
|
|
113
|
+
date,
|
|
114
|
+
bodies: list.map((c) => c.body),
|
|
115
|
+
})
|
|
116
|
+
const path = resolve(cwd, pkg.path, 'CHANGELOG.md')
|
|
117
|
+
let existing: string | null = null
|
|
118
|
+
if (await fileExists(path)) {
|
|
119
|
+
existing = await readFile(path, 'utf8')
|
|
120
|
+
}
|
|
121
|
+
await writeFile(path, prependChangelog(existing, entry))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Delete consumed changeset files
|
|
125
|
+
for (const cs of changesets) {
|
|
126
|
+
await unlink(cs.file)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return bumped
|
|
131
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import { loadConfig } from '../config/index.js'
|
|
3
|
+
import { defaultSpawn, runOrThrow, type SpawnFn } from '../utils/npm.js'
|
|
4
|
+
|
|
5
|
+
export type DeployCommandOptions = {
|
|
6
|
+
cwd: string
|
|
7
|
+
pkg?: string
|
|
8
|
+
env?: string
|
|
9
|
+
spawn?: SpawnFn
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function pnpmExec(bin: string, ...args: string[]): string[] {
|
|
13
|
+
return ['pnpm', 'exec', bin, ...args]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function deploy(opts: DeployCommandOptions): Promise<void> {
|
|
17
|
+
const spawn = opts.spawn ?? defaultSpawn
|
|
18
|
+
const cwd = resolve(opts.cwd)
|
|
19
|
+
const cfg = loadConfig(cwd)
|
|
20
|
+
|
|
21
|
+
let targets = cfg.packages
|
|
22
|
+
if (opts.pkg !== undefined) {
|
|
23
|
+
const match = cfg.packages.find((p) => p.name === opts.pkg)
|
|
24
|
+
if (!match) {
|
|
25
|
+
throw new Error(`package not found: ${opts.pkg}`)
|
|
26
|
+
}
|
|
27
|
+
if (match.type !== 'webui' && match.type !== 'api') {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`package ${opts.pkg} (type=${match.type}) is not deployable; cannot deploy non-webui/api packages`,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
targets = [match]
|
|
33
|
+
} else {
|
|
34
|
+
targets = cfg.packages.filter((p) => p.type === 'webui' || p.type === 'api')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const pkg of targets) {
|
|
38
|
+
const pkgDir = resolve(cwd, pkg.path)
|
|
39
|
+
let argv: string[]
|
|
40
|
+
if (pkg.type === 'webui') {
|
|
41
|
+
argv = pnpmExec('wrangler', 'pages', 'deploy', 'dist')
|
|
42
|
+
} else if (pkg.type === 'api') {
|
|
43
|
+
argv = pnpmExec('wrangler', 'deploy')
|
|
44
|
+
} else {
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
if (opts.env !== undefined) {
|
|
48
|
+
argv.push('--env', opts.env)
|
|
49
|
+
}
|
|
50
|
+
await runOrThrow(spawn, argv, pkgDir)
|
|
51
|
+
}
|
|
52
|
+
}
|