Skip to content
Open
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -710,11 +710,11 @@ When you run `domstack --eject`, it will:

It is recomended to eject early in your project so that you can customize the root layout as you see fit, and de-couple yourself from potential unwanted changes in the default layout as new versions of DOMStack are released.

### `--copy` directories
### `--copy` directories and files

You can specify directories to copy into your `dest` directory using the `--copy` flag. Everything in those directories will be copied as-is into the destination, including js, css, html and markdown, preserving the internal directory structure. Conflicting files are not detected or reported and will cause undefined behavior.
You can specify directories or individual files to copy into your `dest` directory using the `--copy` flag. Everything in those directories will be copied as-is into the destination, including js, css, html and markdown, preserving the internal directory structure. Individual files (such as a `_redirects` or `sw.js`) are copied to the root of the destination. Conflicting files are not detected or reported and will cause undefined behavior.

Copy folders must live **outside** of the `dest` directory. Copy directories can be in the src directory allowing for nested builds. In this case they are added to the ignore glob and ignored by the rest of `domstack`.
Copy paths must live **outside** of the `dest` directory. Copy directories can be in the src directory allowing for nested builds. In this case they are added to the ignore glob and ignored by the rest of `domstack`.

This is useful when you have legacy or archived site content that you want to include in your site, but don't want `domstack` to process or modify it.
In general, static content should live in your primary `src` directory, however for merging in old static assets over your domstack build is sometimes easier to reason about when it's kept in a separate folder and isn't processed in any way.
Expand Down
2 changes: 1 addition & 1 deletion bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const options = {
},
copy: {
type: 'string',
help: 'path to directories to copy into dist; can be used multiple times',
help: 'path to directories or individual files to copy into dist; can be used multiple times',
multiple: true
},
help: {
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export class DomStack {
await this.#rebuildMaps(siteData)

// ── Copy watchers & browser-sync ─────────────────────────────────────
const copyDirs = getCopyDirs(this.opts.copy)
const copyDirs = await getCopyDirs(this.opts.copy)

this.#cpxWatchers = [
cpx.watch(getCopyGlob(this.#src), this.#dest, { ignore: this.opts.ignore }),
Expand Down
32 changes: 22 additions & 10 deletions lib/build-copy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// @ts-expect-error
import cpx from 'cpx2'
import { join } from 'node:path'
import { stat } from 'node:fs/promises'
const copy = cpx.copy

/**
Expand All @@ -15,11 +16,22 @@ const copy = cpx.copy

/**
* @param {string[]} copy
* @return {string[]}
* @return {Promise<string[]>}
*/
export function getCopyDirs (copy = []) {
const copyGlobs = copy?.map((dir) => join(dir, '**'))
return copyGlobs
export async function getCopyDirs (copy = []) {
const globs = await Promise.all(copy.map(async (entry) => {
try {
const stats = await stat(entry)
if (stats.isDirectory()) {
return join(entry, '**')
}
} catch {
// Path not accessible yet — treat as directory glob
return join(entry, '**')
}
return entry
}))
return globs
Comment thread
bcomnes marked this conversation as resolved.
}

/**
Expand All @@ -36,22 +48,22 @@ export async function buildCopy (_src, dest, _siteData, opts) {
warnings: [],
}

const copyDirs = getCopyDirs(opts?.copy)
const copyGlobs = await getCopyDirs(opts?.copy)

const copyTasks = copyDirs.map((copyDir) => {
return copy(copyDir, dest)
const copyTasks = copyGlobs.map((copyGlob) => {
return copy(copyGlob, dest)
})

const settled = await Promise.allSettled(copyTasks)

for (const [index, result] of Object.entries(settled)) {
// @ts-expect-error
const copyDir = copyDirs[index]
const copyGlob = copyGlobs[index]
if (result.status === 'rejected') {
const buildError = new Error('Error copying copy folders', { cause: result.reason })
const buildError = new Error(`Error copying copy path: ${copyGlob}`, { cause: result.reason })
results.errors.push(buildError)
} else {
results.report[copyDir] = result.value
results.report[copyGlob] = result.value
}
}
return results
Expand Down
36 changes: 33 additions & 3 deletions lib/build-copy/index.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
import { test } from 'node:test'
import assert from 'node:assert'
import { mkdtemp, writeFile, rm } from 'node:fs/promises'
import { join } from 'node:path'
import os from 'node:os'
import { getCopyDirs } from './index.js'

test.describe('build-copy', () => {
test('getCopyDirs returns correct src/dest pairs', async () => {
const copyDirs = getCopyDirs(['fixtures'])
test('getCopyDirs appends ** for non-existent paths', async () => {
const dir = await mkdtemp(join(os.tmpdir(), 'domstack-copy-test-'))
const missingPath = join(dir, 'does-not-exist')
try {
const copyDirs = await getCopyDirs([missingPath])
assert.deepStrictEqual(copyDirs, [join(missingPath, '**')])
} finally {
await rm(dir, { recursive: true, force: true })
}
})

test('getCopyDirs returns file path as-is for existing files', async () => {
const dir = await mkdtemp(join(os.tmpdir(), 'domstack-copy-test-'))
const file = join(dir, 'sw.js')
try {
await writeFile(file, 'self.addEventListener("fetch", () => {})')
const result = await getCopyDirs([file])
assert.deepStrictEqual(result, [file])
} finally {
await rm(dir, { recursive: true, force: true })
}
})

assert.deepStrictEqual(copyDirs, ['fixtures/**'])
test('getCopyDirs appends ** for existing directories', async () => {
const dir = await mkdtemp(join(os.tmpdir(), 'domstack-copy-test-'))
try {
const result = await getCopyDirs([dir])
assert.deepStrictEqual(result, [join(dir, '**')])
} finally {
await rm(dir, { recursive: true, force: true })
}
})
Comment thread
bcomnes marked this conversation as resolved.
})