Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"vitest": "^3.1.1"
},
"dependencies": {
"fdir": "^6.4.3",
"gunshi": "^0.14.0"
"gunshi": "^0.14.0",
"tinyglobby": "^0.2.12"
}
}
109 changes: 69 additions & 40 deletions src/commands/publish.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import {x} from 'tinyexec';
import {Command, define} from 'gunshi';
import path from 'node:path';
import {glob} from 'tinyglobby';
import * as prompts from '@clack/prompts';
import {rm} from 'node:fs/promises';
import {
type PackageJson,
preparePackageJson,
readPackageJson
} from '../utils/package-json.js';
import {copyFileToDir, getTempDir} from '../utils/fs.js';
import {
copyRelativeFilesToDir,
getSourceFilesFromPaths,
getTempDir
} from '../utils/fs.js';
import {updateSourceMapUrls} from '../utils/sourcemaps.js';
ExtractedSourceMapSuccess,
extractSourceMaps,
updateSourceMapUrls
} from '../utils/sourcemaps.js';

const filesToKeep = ['.npmrc', '.npmignore', 'package.json'];

Expand All @@ -37,43 +38,76 @@ export const publishCommand: Command<typeof options> = define({
prompts.intro('Publishing sourcemaps...');

const cwd = process.cwd();
const paths = ctx.positionals.length > 0 ? ctx.positionals : ['dist/'];
const dryRun = ctx.values['dry-run'];
const provenance = ctx.values.provenance;

const tempDir = await getTempDir(cwd, '.sourcemap-publish');
const packageJsonPath = path.join(cwd, 'package.json');
let packageJson: PackageJson;

try {
await copyRelativeFilesToDir([...filesToKeep, ...paths], cwd, tempDir);
packageJson = await readPackageJson(packageJsonPath);
} catch (err) {
prompts.log.error(`${err}`);
prompts.cancel(
'Failed to read package.json. Please ensure you run this command in the project directory'
);
return;
}

const packageJsonPath = path.join(tempDir, 'package.json');
let packageJson: PackageJson | null;
let paths: string[];

try {
packageJson = await readPackageJson(packageJsonPath);
} catch (err) {
prompts.log.error(`${err}`);
prompts.cancel(
'Failed to read package.json. Please ensure you run this command in the project directory'
);
try {
paths = await glob(packageJson.files, {
absolute: true,
cwd,
onlyFiles: true
});
} catch (err) {
prompts.cancel(
'Failed to load files from `files` array in package.json.'
);
prompts.log.message(String(err));
return;
}

let tempDir: string | undefined;

try {
tempDir = await getTempDir(cwd, '.sourcemap-publish');

const tempPackageJsonPath = path.join(tempDir, 'package.json');

const sourcePaths = paths.filter((p) => p.endsWith('.js'));
const sourceMaps = await extractSourceMaps(sourcePaths);

if (sourceMaps.length === 0) {
prompts.cancel('No sourcemap files were found to publish!');
return;
}

const resolvedSourcePaths = paths.map((p) => path.join(cwd, p));
const successfulSourceMaps: ExtractedSourceMapSuccess[] = [];

const files = await getSourceFilesFromPaths(cwd, resolvedSourcePaths);
for (const sourceMap of sourceMaps) {
if (sourceMap.success === false) {
prompts.log.warn(
`Skipping source file "${sourceMap.source}" (${sourceMap.reason})`
);
continue;
}

if (files.length === 0) {
prompts.cancel('No files were found to publish!');
return;
successfulSourceMaps.push(sourceMap);
await copyFileToDir(sourceMap.path, cwd, tempDir);
}

for (const file of filesToKeep) {
await copyFileToDir(path.join(cwd, file), cwd, tempDir);
}

try {
packageJson = await preparePackageJson(
tempDir,
packageJsonPath,
packageJson,
paths
tempPackageJsonPath,
packageJson
);
} catch (err) {
prompts.log.error(`${err}`);
Expand All @@ -82,26 +116,19 @@ export const publishCommand: Command<typeof options> = define({
}

try {
const totalSuccessfulSourceMaps = successfulSourceMaps.length;
const totalFailedSourceMaps =
sourceMaps.length - totalSuccessfulSourceMaps;

if (dryRun) {
prompts.log.info(
`Updated ${files.length} sourcemap URLs, skipped 0 files (dry run)`
`Updated ${totalSuccessfulSourceMaps} sourcemap URLs, skipped ${totalFailedSourceMaps} files (dry run)`
);
} else {
const updateResult = await updateSourceMapUrls(
cwd,
files,
packageJson
);
const totalSkipped = updateResult.skipped.length;
const totalUpdated = files.length - totalSkipped;
await updateSourceMapUrls(cwd, successfulSourceMaps, packageJson);
prompts.log.info(
`Updated ${totalUpdated} sourcemap URLs, skipped ${totalSkipped} files`
`Updated ${totalSuccessfulSourceMaps} sourcemap URLs, skipped ${totalFailedSourceMaps} files`
);
for (const skippedFile of updateResult.skipped) {
prompts.log.warn(
`Skipped ${skippedFile} (could not load file or sourcemap)`
);
}
}
} catch (err) {
prompts.log.error(`${err}`);
Expand Down Expand Up @@ -152,7 +179,9 @@ export const publishCommand: Command<typeof options> = define({
`Published sourcemaps successfully!${dryRun ? ' (dry run)' : ''}`
);
} finally {
await rm(tempDir, {force: true, recursive: true});
if (tempDir) {
await rm(tempDir, {force: true, recursive: true});
}
}
}
});
8 changes: 0 additions & 8 deletions src/utils/__snapshots__/fs.test.ts.snap

This file was deleted.

36 changes: 25 additions & 11 deletions src/utils/__snapshots__/sourcemaps.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`updateSourceMapUrls > replaces urls with CDN urls 1`] = `
"
// This is a test file
//# sourceMappingURL=https://unpkg.com/[email protected]/foo.js.map"
exports[`extractSourceMap > retrieves sourcemap URL 1`] = `
{
"path": "TEMP_DIR/foo.js.map",
"range": [
21,
31,
],
"source": "TEMP_DIR/foo.js",
"success": true,
}
`;

exports[`updateSourceMapUrls > replaces urls with CDN urls 2`] = `
exports[`extractSourceMaps > extracts sourcemaps from files 1`] = `
{
"path": "TEMP_DIR/foo.js.map",
"range": [
21,
31,
],
"source": "TEMP_DIR/foo.js",
"success": true,
}
`;

exports[`updateSourceMapUrls > replaces urls with CDN urls 1`] = `
"
// This is a test file
//# sourceMappingURL=https://unpkg.com/[email protected]/foo.js.map"
`;

exports[`updateSourceMapUrls > replaces urls with CDN urls 3`] = `"// x"`;

exports[`updateSourceMapUrls > replaces urls with CDN urls 4`] = `"// x"`;

exports[`updateSourceMapUrls > skips on non-existent sourcemap 1`] = `
exports[`updateSourceMapUrls > replaces urls with CDN urls 2`] = `
"
// This is a test file
//# sourceMappingURL=https://unpkg.com/[email protected]/foo.js.map"
//# sourceMappingURL=https://unpkg.com/[email protected]/bar.js.map"
`;
57 changes: 11 additions & 46 deletions src/utils/fs.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import {suite, beforeEach, afterEach, test, expect} from 'vitest';
import {writeFile, rm, mkdtemp, mkdir, stat} from 'node:fs/promises';
import {
copyRelativeFilesToDir,
getSourceFilesFromPaths,
getTempDir
} from './fs.js';
import {copyFileToDir, getTempDir} from './fs.js';
import path from 'node:path';
import {tmpdir} from 'node:os';

const mockFs: Record<string, string> = {
'lib/js-file.js': '// foo',
'lib/ts-file.ts': '// foo',
'lib/dts-file.d.ts': '// foo',
'lib/nested/js-file.js': '// foo'
'lib/file.js': '// foo',
'lib/file.d.ts': '// foo'
};

const writeMockFs = async (tempDir: string) => {
Expand All @@ -23,29 +17,6 @@ const writeMockFs = async (tempDir: string) => {
}
};

suite('getSourceFilesFromPaths', () => {
let tempDir: string;

beforeEach(async () => {
tempDir = await mkdtemp(path.join(tmpdir(), 'smpub'));
await writeMockFs(tempDir);
});

afterEach(async () => {
await rm(tempDir, {force: true, recursive: true});
});

test('should find all js/ts files in the specified paths', async () => {
const results = await getSourceFilesFromPaths(tempDir, [
path.join(tempDir, 'lib/')
]);

expect(
results.map((p) => p.replace(tempDir, '<TEMPDIR>'))
).toMatchSnapshot();
});
});

suite('getTempDir', () => {
let tempDir: string;

Expand All @@ -65,7 +36,7 @@ suite('getTempDir', () => {
});
});

suite('copyRelativeFilesToDir', () => {
suite('copyFileToDir', () => {
let tempDir: string;
let targetDir: string;

Expand All @@ -80,27 +51,21 @@ suite('copyRelativeFilesToDir', () => {
await rm(targetDir, {force: true, recursive: true});
});

test('copies files to target directory', async () => {
const files = ['lib/js-file.js', 'lib/ts-file.ts'];
await copyRelativeFilesToDir(files, tempDir, targetDir);
test('copies file to target directory', async () => {
const file = path.join(tempDir, 'lib/file.js');
await copyFileToDir(file, tempDir, targetDir);

await expect(
stat(path.join(targetDir, 'lib/js-file.js'))
).resolves.not.toThrow();
await expect(
stat(path.join(targetDir, 'lib/ts-file.ts'))
stat(path.join(targetDir, 'lib/file.js'))
).resolves.not.toThrow();
});

test('ignores non-existent files', async () => {
const files = ['lib/js-file.js', 'lib/non-existent-file.js'];
await copyRelativeFilesToDir(files, tempDir, targetDir);
const file = path.join(tempDir, 'lib/non-existent.js');
await copyFileToDir(file, tempDir, targetDir);

await expect(
stat(path.join(targetDir, 'lib/js-file.js'))
).resolves.not.toThrow();
await expect(
stat(path.join(targetDir, 'lib/non-existent-file.js'))
stat(path.join(targetDir, 'lib/non-existent.js'))
).rejects.toThrow();
});
});
Loading