@napi-rs/cli 3.0.0-alpha.1 → 3.0.0-alpha.100

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.
Files changed (176) hide show
  1. package/cli.mjs +1 -1
  2. package/dist/api/__tests__/create-npm-dirs.spec.d.ts +1 -0
  3. package/dist/api/__tests__/new.spec.d.ts +1 -0
  4. package/dist/api/build.d.ts +2 -2
  5. package/dist/api/create-npm-dirs.d.ts +5 -0
  6. package/dist/api/templates/index.d.ts +0 -7
  7. package/dist/api/templates/js-binding.d.ts +2 -1
  8. package/dist/api/templates/load-wasi-template.d.ts +2 -0
  9. package/dist/api/templates/wasi-worker-template.d.ts +2 -0
  10. package/dist/cli.js +3961 -14
  11. package/dist/def/artifacts.d.ts +21 -1
  12. package/dist/def/build.d.ts +167 -4
  13. package/dist/def/create-npm-dirs.d.ts +10 -0
  14. package/dist/def/new.d.ts +25 -7
  15. package/dist/def/pre-publish.d.ts +21 -2
  16. package/dist/def/rename.d.ts +16 -6
  17. package/dist/def/universalize.d.ts +10 -0
  18. package/dist/def/version.d.ts +10 -0
  19. package/dist/index.cjs +10142 -20524
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +3711 -29
  22. package/dist/utils/__tests__/config.spec.d.ts +1 -0
  23. package/dist/utils/config.d.ts +70 -6
  24. package/dist/utils/index.d.ts +1 -1
  25. package/dist/utils/metadata.d.ts +13 -1
  26. package/dist/utils/misc.d.ts +3 -3
  27. package/dist/utils/read-config.d.ts +7 -0
  28. package/dist/utils/target.d.ts +9 -6
  29. package/dist/utils/typegen.d.ts +4 -1
  30. package/dist/utils/version.d.ts +2 -1
  31. package/package.json +60 -24
  32. package/src/api/__tests__/create-npm-dirs.spec.ts +210 -0
  33. package/src/api/__tests__/new.spec.ts +429 -0
  34. package/src/api/artifacts.ts +83 -8
  35. package/src/api/build.ts +682 -134
  36. package/src/api/create-npm-dirs.ts +64 -13
  37. package/src/api/new.ts +389 -125
  38. package/src/api/pre-publish.ts +27 -16
  39. package/src/api/rename.ts +103 -17
  40. package/src/api/templates/index.ts +0 -7
  41. package/src/api/templates/js-binding.ts +212 -107
  42. package/src/api/templates/load-wasi-template.ts +216 -0
  43. package/src/api/templates/wasi-worker-template.ts +122 -0
  44. package/src/api/universalize.ts +13 -8
  45. package/src/api/version.ts +5 -2
  46. package/src/commands/artifacts.ts +2 -2
  47. package/src/commands/build.ts +1 -1
  48. package/src/commands/new.ts +46 -49
  49. package/src/def/artifacts.ts +22 -3
  50. package/src/def/build.ts +83 -8
  51. package/src/def/create-npm-dirs.ts +9 -0
  52. package/src/def/new.ts +28 -3
  53. package/src/def/pre-publish.ts +24 -3
  54. package/src/def/rename.ts +11 -2
  55. package/src/def/universalize.ts +9 -0
  56. package/src/def/version.ts +9 -0
  57. package/src/index.ts +2 -0
  58. package/src/utils/__tests__/__fixtures__/napi_type_def +130 -130
  59. package/src/utils/__tests__/__snapshots__/config.spec.ts.md +97 -0
  60. package/src/utils/__tests__/__snapshots__/config.spec.ts.snap +0 -0
  61. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +56 -0
  62. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  63. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +703 -164
  64. package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
  65. package/src/utils/__tests__/__snapshots__/version.spec.ts.md +9 -8
  66. package/src/utils/__tests__/__snapshots__/version.spec.ts.snap +0 -0
  67. package/src/utils/__tests__/config.spec.ts +81 -0
  68. package/src/utils/__tests__/typegen.spec.ts +19 -4
  69. package/src/utils/cargo.ts +2 -2
  70. package/src/utils/config.ts +147 -13
  71. package/src/utils/index.ts +1 -1
  72. package/src/utils/log.ts +1 -1
  73. package/src/utils/metadata.ts +52 -14
  74. package/src/utils/misc.ts +21 -7
  75. package/src/utils/read-config.ts +20 -0
  76. package/src/utils/target.ts +53 -9
  77. package/src/utils/typegen.ts +100 -40
  78. package/src/utils/version.ts +11 -9
  79. package/dist/api/artifacts.js +0 -50
  80. package/dist/api/build.js +0 -404
  81. package/dist/api/create-npm-dirs.js +0 -63
  82. package/dist/api/new.js +0 -154
  83. package/dist/api/pre-publish.js +0 -157
  84. package/dist/api/rename.js +0 -28
  85. package/dist/api/templates/.gitignore.d.ts +0 -1
  86. package/dist/api/templates/.gitignore.js +0 -198
  87. package/dist/api/templates/.npmignore.d.ts +0 -1
  88. package/dist/api/templates/.npmignore.js +0 -14
  89. package/dist/api/templates/build.rs.d.ts +0 -1
  90. package/dist/api/templates/build.rs.js +0 -4
  91. package/dist/api/templates/cargo.toml.d.ts +0 -6
  92. package/dist/api/templates/cargo.toml.js +0 -25
  93. package/dist/api/templates/ci-template.d.ts +0 -1
  94. package/dist/api/templates/ci-template.js +0 -566
  95. package/dist/api/templates/ci.yml.d.ts +0 -1
  96. package/dist/api/templates/ci.yml.js +0 -92
  97. package/dist/api/templates/index.js +0 -8
  98. package/dist/api/templates/js-binding.js +0 -124
  99. package/dist/api/templates/lib.rs.d.ts +0 -1
  100. package/dist/api/templates/lib.rs.js +0 -10
  101. package/dist/api/templates/package.json.d.ts +0 -8
  102. package/dist/api/templates/package.json.js +0 -30
  103. package/dist/api/universalize.js +0 -38
  104. package/dist/api/version.js +0 -16
  105. package/dist/commands/artifacts.js +0 -19
  106. package/dist/commands/build.js +0 -34
  107. package/dist/commands/create-npm-dirs.js +0 -7
  108. package/dist/commands/help.js +0 -12
  109. package/dist/commands/new.js +0 -112
  110. package/dist/commands/pre-publish.js +0 -8
  111. package/dist/commands/rename.js +0 -7
  112. package/dist/commands/universalize.js +0 -7
  113. package/dist/commands/version.js +0 -7
  114. package/dist/def/artifacts.js +0 -38
  115. package/dist/def/build.js +0 -105
  116. package/dist/def/commands/artifacts.d.ts +0 -15
  117. package/dist/def/commands/artifacts.js +0 -29
  118. package/dist/def/commands/build.d.ts +0 -53
  119. package/dist/def/commands/build.js +0 -105
  120. package/dist/def/commands/create-npm-dirs.d.ts +0 -15
  121. package/dist/def/commands/create-npm-dirs.js +0 -29
  122. package/dist/def/commands/new.d.ts +0 -27
  123. package/dist/def/commands/new.js +0 -53
  124. package/dist/def/commands/pre-publish.d.ts +0 -23
  125. package/dist/def/commands/pre-publish.js +0 -45
  126. package/dist/def/commands/rename.d.ts +0 -25
  127. package/dist/def/commands/rename.js +0 -49
  128. package/dist/def/commands/universalize.d.ts +0 -13
  129. package/dist/def/commands/universalize.js +0 -25
  130. package/dist/def/commands/version.d.ts +0 -13
  131. package/dist/def/commands/version.js +0 -25
  132. package/dist/def/create-npm-dirs.js +0 -38
  133. package/dist/def/new.js +0 -66
  134. package/dist/def/options/artifacts.d.ts +0 -35
  135. package/dist/def/options/artifacts.js +0 -11
  136. package/dist/def/options/build.d.ts +0 -97
  137. package/dist/def/options/build.js +0 -3
  138. package/dist/def/options/create-npm-dirs.d.ts +0 -35
  139. package/dist/def/options/create-npm-dirs.js +0 -11
  140. package/dist/def/options/new.d.ts +0 -79
  141. package/dist/def/options/new.js +0 -15
  142. package/dist/def/options/pre-publish.d.ts +0 -65
  143. package/dist/def/options/pre-publish.js +0 -13
  144. package/dist/def/options/rename.d.ts +0 -75
  145. package/dist/def/options/rename.js +0 -11
  146. package/dist/def/options/universalize.d.ts +0 -28
  147. package/dist/def/options/universalize.js +0 -10
  148. package/dist/def/options/version.d.ts +0 -28
  149. package/dist/def/options/version.js +0 -10
  150. package/dist/def/pre-publish.js +0 -56
  151. package/dist/def/rename.js +0 -58
  152. package/dist/def/universalize.js +0 -33
  153. package/dist/def/version.js +0 -33
  154. package/dist/utils/__tests__/target.spec.js +0 -10
  155. package/dist/utils/__tests__/typegen.spec.js +0 -37
  156. package/dist/utils/__tests__/version.spec.js +0 -5
  157. package/dist/utils/cargo.js +0 -33
  158. package/dist/utils/ci.d.ts +0 -11
  159. package/dist/utils/ci.js +0 -86
  160. package/dist/utils/config.js +0 -42
  161. package/dist/utils/index.js +0 -9
  162. package/dist/utils/log.js +0 -14
  163. package/dist/utils/metadata.js +0 -19
  164. package/dist/utils/misc.js +0 -35
  165. package/dist/utils/target.js +0 -109
  166. package/dist/utils/typegen.js +0 -158
  167. package/dist/utils/version.js +0 -59
  168. package/src/api/templates/.gitignore.ts +0 -198
  169. package/src/api/templates/.npmignore.ts +0 -14
  170. package/src/api/templates/build.rs.ts +0 -4
  171. package/src/api/templates/cargo.toml.ts +0 -35
  172. package/src/api/templates/ci-template.ts +0 -566
  173. package/src/api/templates/ci.yml.ts +0 -116
  174. package/src/api/templates/lib.rs.ts +0 -10
  175. package/src/api/templates/package.json.ts +0 -44
  176. package/src/utils/ci.ts +0 -102
package/src/api/new.ts CHANGED
@@ -1,4 +1,10 @@
1
- import path from 'path'
1
+ import { exec, execSync } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { homedir } from 'node:os'
4
+ import path from 'node:path'
5
+ import { promises as fs } from 'node:fs'
6
+
7
+ import { load as yamlLoad, dump as yamlDump } from 'js-yaml'
2
8
 
3
9
  import {
4
10
  applyDefaultNewOptions,
@@ -6,35 +12,289 @@ import {
6
12
  } from '../def/new.js'
7
13
  import {
8
14
  AVAILABLE_TARGETS,
9
- CLI_VERSION,
10
15
  debugFactory,
11
16
  DEFAULT_TARGETS,
12
17
  mkdirAsync,
13
- writeFileAsync,
18
+ readdirAsync,
19
+ statAsync,
20
+ SupportedPackageManager,
14
21
  } from '../utils/index.js'
15
22
  import { napiEngineRequirement } from '../utils/version.js'
23
+ import { renameProject } from './rename.js'
16
24
 
17
- import {
18
- createBuildRs,
19
- createCargoToml,
20
- createGithubActionsCIYml,
21
- createLibRs,
22
- createPackageJson,
23
- gitIgnore,
24
- npmIgnore,
25
- } from './templates/index.js'
25
+ // Template imports removed as we're now using external templates
26
26
 
27
27
  const debug = debugFactory('new')
28
28
 
29
- interface Output {
30
- target: string
31
- content: string
29
+ type NewOptions = Required<RawNewOptions>
30
+
31
+ const TEMPLATE_REPOS = {
32
+ yarn: 'https://github.com/napi-rs/package-template',
33
+ pnpm: 'https://github.com/napi-rs/package-template-pnpm',
34
+ } as const
35
+
36
+ async function checkGitCommand(): Promise<boolean> {
37
+ try {
38
+ await new Promise((resolve) => {
39
+ const cp = exec('git --version')
40
+ cp.on('error', () => {
41
+ resolve(false)
42
+ })
43
+ cp.on('exit', (code) => {
44
+ if (code === 0) {
45
+ resolve(true)
46
+ } else {
47
+ resolve(false)
48
+ }
49
+ })
50
+ })
51
+ return true
52
+ } catch {
53
+ return false
54
+ }
55
+ }
56
+
57
+ async function ensureCacheDir(
58
+ packageManager: SupportedPackageManager,
59
+ ): Promise<string> {
60
+ const cacheDir = path.join(homedir(), '.napi-rs', 'template', packageManager)
61
+ await mkdirAsync(cacheDir, { recursive: true })
62
+ return cacheDir
32
63
  }
33
64
 
34
- type NewOptions = Required<RawNewOptions>
65
+ async function downloadTemplate(
66
+ packageManager: SupportedPackageManager,
67
+ cacheDir: string,
68
+ ): Promise<void> {
69
+ const repoUrl = TEMPLATE_REPOS[packageManager]
70
+ const templatePath = path.join(cacheDir, 'repo')
71
+
72
+ if (existsSync(templatePath)) {
73
+ debug(`Template cache found at ${templatePath}, updating...`)
74
+ try {
75
+ // Fetch latest changes and reset to remote
76
+ await new Promise<void>((resolve, reject) => {
77
+ const cp = exec('git fetch origin', { cwd: templatePath })
78
+ cp.on('error', reject)
79
+ cp.on('exit', (code) => {
80
+ if (code === 0) {
81
+ resolve()
82
+ } else {
83
+ reject(
84
+ new Error(
85
+ `Failed to fetch latest changes, git process exited with code ${code}`,
86
+ ),
87
+ )
88
+ }
89
+ })
90
+ })
91
+ execSync('git reset --hard origin/main', {
92
+ cwd: templatePath,
93
+ stdio: 'ignore',
94
+ })
95
+ debug('Template updated successfully')
96
+ } catch (error) {
97
+ debug(`Failed to update template: ${error}`)
98
+ throw new Error(`Failed to update template from ${repoUrl}: ${error}`)
99
+ }
100
+ } else {
101
+ debug(`Cloning template from ${repoUrl}...`)
102
+ try {
103
+ execSync(`git clone ${repoUrl} repo`, { cwd: cacheDir, stdio: 'inherit' })
104
+ debug('Template cloned successfully')
105
+ } catch (error) {
106
+ throw new Error(`Failed to clone template from ${repoUrl}: ${error}`)
107
+ }
108
+ }
109
+ }
110
+
111
+ async function copyDirectory(
112
+ src: string,
113
+ dest: string,
114
+ includeWasiBindings: boolean,
115
+ ): Promise<void> {
116
+ await mkdirAsync(dest, { recursive: true })
117
+ const entries = await fs.readdir(src, { withFileTypes: true })
118
+
119
+ for (const entry of entries) {
120
+ const srcPath = path.join(src, entry.name)
121
+ const destPath = path.join(dest, entry.name)
122
+
123
+ // Skip .git directory
124
+ if (entry.name === '.git') {
125
+ continue
126
+ }
127
+
128
+ if (entry.isDirectory()) {
129
+ await copyDirectory(srcPath, destPath, includeWasiBindings)
130
+ } else {
131
+ if (
132
+ !includeWasiBindings &&
133
+ (entry.name.endsWith('.wasi-browser.js') ||
134
+ entry.name.endsWith('.wasi.cjs') ||
135
+ entry.name.endsWith('wasi-worker.browser.mjs ') ||
136
+ entry.name.endsWith('wasi-worker.mjs') ||
137
+ entry.name.endsWith('browser.js'))
138
+ ) {
139
+ continue
140
+ }
141
+ await fs.copyFile(srcPath, destPath)
142
+ }
143
+ }
144
+ }
145
+
146
+ async function filterTargetsInPackageJson(
147
+ filePath: string,
148
+ enabledTargets: string[],
149
+ ): Promise<void> {
150
+ const content = await fs.readFile(filePath, 'utf-8')
151
+ const packageJson = JSON.parse(content)
152
+
153
+ // Filter napi.targets
154
+ if (packageJson.napi?.targets) {
155
+ packageJson.napi.targets = packageJson.napi.targets.filter(
156
+ (target: string) => enabledTargets.includes(target),
157
+ )
158
+ }
159
+
160
+ await fs.writeFile(filePath, JSON.stringify(packageJson, null, 2) + '\n')
161
+ }
162
+
163
+ async function filterTargetsInGithubActions(
164
+ filePath: string,
165
+ enabledTargets: string[],
166
+ ): Promise<void> {
167
+ const content = await fs.readFile(filePath, 'utf-8')
168
+ const yaml = yamlLoad(content) as any
169
+
170
+ const macOSAndWindowsTargets = new Set([
171
+ 'x86_64-pc-windows-msvc',
172
+ 'aarch64-pc-windows-msvc',
173
+ 'x86_64-apple-darwin',
174
+ ])
175
+
176
+ const linuxTargets = new Set([
177
+ 'x86_64-unknown-linux-gnu',
178
+ 'x86_64-unknown-linux-musl',
179
+ 'aarch64-unknown-linux-gnu',
180
+ 'aarch64-unknown-linux-musl',
181
+ 'armv7-unknown-linux-gnueabihf',
182
+ 'armv7-unknown-linux-musleabihf',
183
+ 'riscv64gc-unknown-linux-gnu',
184
+ 'powerpc64le-unknown-linux-gnu',
185
+ 's390x-unknown-linux-gnu',
186
+ 'aarch64-linux-android',
187
+ 'armv7-linux-androideabi',
188
+ ])
189
+
190
+ // Check if any Linux targets are enabled
191
+ const hasLinuxTargets = enabledTargets.some((target) =>
192
+ linuxTargets.has(target),
193
+ )
194
+
195
+ // Filter the matrix configurations in the build job
196
+ if (yaml?.jobs?.build?.strategy?.matrix?.settings) {
197
+ yaml.jobs.build.strategy.matrix.settings =
198
+ yaml.jobs.build.strategy.matrix.settings.filter((setting: any) => {
199
+ if (setting.target) {
200
+ return enabledTargets.includes(setting.target)
201
+ }
202
+ return true
203
+ })
204
+ }
205
+
206
+ const jobsToRemove: string[] = []
207
+
208
+ if (enabledTargets.every((target) => !macOSAndWindowsTargets.has(target))) {
209
+ jobsToRemove.push('test-macOS-windows-binding')
210
+ } else {
211
+ // Filter the matrix configurations in the test-macOS-windows-binding job
212
+ if (
213
+ yaml?.jobs?.['test-macOS-windows-binding']?.strategy?.matrix?.settings
214
+ ) {
215
+ yaml.jobs['test-macOS-windows-binding'].strategy.matrix.settings =
216
+ yaml.jobs['test-macOS-windows-binding'].strategy.matrix.settings.filter(
217
+ (setting: any) => {
218
+ if (setting.target) {
219
+ return enabledTargets.includes(setting.target)
220
+ }
221
+ return true
222
+ },
223
+ )
224
+ }
225
+ }
226
+
227
+ // If no Linux targets are enabled, remove Linux-specific jobs
228
+ if (!hasLinuxTargets) {
229
+ // Remove test-linux-binding job
230
+ if (yaml?.jobs?.['test-linux-binding']) {
231
+ jobsToRemove.push('test-linux-binding')
232
+ }
233
+ } else {
234
+ // Filter the matrix configurations in the test-linux-x64-gnu-binding job
235
+ if (yaml?.jobs?.['test-linux-binding']?.strategy?.matrix?.target) {
236
+ yaml.jobs['test-linux-binding'].strategy.matrix.target = yaml.jobs[
237
+ 'test-linux-binding'
238
+ ].strategy.matrix.target.filter((target: string) => {
239
+ if (target) {
240
+ return enabledTargets.includes(target)
241
+ }
242
+ return true
243
+ })
244
+ }
245
+ }
246
+
247
+ if (!enabledTargets.includes('wasm32-wasip1-threads')) {
248
+ jobsToRemove.push('test-wasi')
249
+ }
250
+
251
+ if (!enabledTargets.includes('x86_64-unknown-freebsd')) {
252
+ jobsToRemove.push('build-freebsd')
253
+ }
254
+
255
+ // Filter other test jobs based on target
256
+ for (const [jobName, jobConfig] of Object.entries(yaml.jobs || {})) {
257
+ if (
258
+ jobName.startsWith('test-') &&
259
+ jobName !== 'test-macOS-windows-binding' &&
260
+ jobName !== 'test-linux-x64-gnu-binding'
261
+ ) {
262
+ // Extract target from job name or config
263
+ const job = jobConfig as any
264
+ if (job.strategy?.matrix?.settings?.[0]?.target) {
265
+ const target = job.strategy.matrix.settings[0].target
266
+ if (!enabledTargets.includes(target)) {
267
+ jobsToRemove.push(jobName)
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ // Remove jobs for disabled targets
274
+ for (const jobName of jobsToRemove) {
275
+ delete yaml.jobs[jobName]
276
+ }
277
+
278
+ if (Array.isArray(yaml.jobs?.publish?.needs)) {
279
+ yaml.jobs.publish.needs = yaml.jobs.publish.needs.filter(
280
+ (need: string) => !jobsToRemove.includes(need),
281
+ )
282
+ }
283
+
284
+ // Write back the filtered YAML
285
+ const updatedYaml = yamlDump(yaml, {
286
+ lineWidth: -1,
287
+ noRefs: true,
288
+ sortKeys: false,
289
+ })
290
+ await fs.writeFile(filePath, updatedYaml)
291
+ }
35
292
 
36
293
  function processOptions(options: RawNewOptions) {
37
294
  debug('Processing options...')
295
+ if (!options.path) {
296
+ throw new Error('Please provide the path as the argument')
297
+ }
38
298
  options.path = path.resolve(process.cwd(), options.path)
39
299
  debug(`Resolved target path to: ${options.path}`)
40
300
 
@@ -54,6 +314,20 @@ function processOptions(options: RawNewOptions) {
54
314
  throw new Error('At least one target must be enabled')
55
315
  }
56
316
  }
317
+ if (
318
+ options.targets.some((target) => target === 'wasm32-wasi-preview1-threads')
319
+ ) {
320
+ const out = execSync(`rustup target list`, {
321
+ encoding: 'utf8',
322
+ })
323
+ if (out.includes('wasm32-wasip1-threads')) {
324
+ options.targets = options.targets.map((target) =>
325
+ target === 'wasm32-wasi-preview1-threads'
326
+ ? 'wasm32-wasip1-threads'
327
+ : target,
328
+ )
329
+ }
330
+ }
57
331
 
58
332
  return applyDefaultNewOptions(options) as NewOptions
59
333
  }
@@ -67,134 +341,124 @@ export async function newProject(userOptions: RawNewOptions) {
67
341
  debug('Targets to be enabled:')
68
342
  debug(options.targets)
69
343
 
70
- const outputs = generateFiles(options)
71
-
72
- try {
73
- debug(`Try to create target directory: ${options.path}`)
74
- if (!options.dryRun) {
75
- await mkdirAsync(options.path, { recursive: true })
76
- }
77
- } catch (e) {
78
- throw new Error(`Failed to create target directory: ${options.path}`, {
79
- cause: e,
80
- })
344
+ // Check if git is available
345
+ if (!(await checkGitCommand())) {
346
+ throw new Error(
347
+ 'Git is not installed or not available in PATH. Please install Git to continue.',
348
+ )
81
349
  }
82
350
 
83
- await dumpOutputs(outputs, options.dryRun)
84
- debug(`Project created at: ${options.path}`)
85
- }
351
+ const packageManager = options.packageManager as SupportedPackageManager
86
352
 
87
- function generateFiles(options: NewOptions): Output[] {
88
- return [
89
- generateCargoToml,
90
- generateLibRs,
91
- generateBuildRs,
92
- generatePackageJson,
93
- generateGithubWorkflow,
94
- generateIgnoreFiles,
95
- ].flatMap((generator) => {
96
- const output = generator(options)
97
-
98
- if (!output) {
99
- return []
100
- }
353
+ // Ensure target directory exists and is empty
354
+ await ensurePath(options.path, options.dryRun)
101
355
 
102
- if (Array.isArray(output)) {
103
- return output.map((o) => ({
104
- ...o,
105
- target: path.join(options.path, o.target),
106
- }))
107
- } else {
108
- return [{ ...output, target: path.join(options.path, output.target) }]
109
- }
110
- })
111
- }
356
+ if (!options.dryRun) {
357
+ try {
358
+ // Download or update template
359
+ const cacheDir = await ensureCacheDir(packageManager)
360
+ await downloadTemplate(packageManager, cacheDir)
112
361
 
113
- function generateCargoToml(options: NewOptions): Output {
114
- return {
115
- target: './Cargo.toml',
116
- content: createCargoToml({
117
- name: options.name,
118
- license: options.license,
119
- features: [`napi${options.minNodeApiVersion}`],
120
- deriveFeatures: options.enableTypeDef ? ['type-def'] : [],
121
- }),
122
- }
123
- }
362
+ // Copy template files to target directory
363
+ const templatePath = path.join(cacheDir, 'repo')
364
+ await copyDirectory(
365
+ templatePath,
366
+ options.path,
367
+ options.targets.includes('wasm32-wasip1-threads'),
368
+ )
124
369
 
125
- function generateLibRs(_options: NewOptions): Output {
126
- return {
127
- target: './src/lib.rs',
128
- content: createLibRs(),
129
- }
130
- }
370
+ // Rename project using the rename API
371
+ await renameProject({
372
+ cwd: options.path,
373
+ name: options.name,
374
+ binaryName: getBinaryName(options.name),
375
+ })
131
376
 
132
- function generateBuildRs(_options: NewOptions): Output {
133
- return {
134
- target: './build.rs',
135
- content: createBuildRs(),
136
- }
137
- }
377
+ // Filter targets in package.json
378
+ const packageJsonPath = path.join(options.path, 'package.json')
379
+ if (existsSync(packageJsonPath)) {
380
+ await filterTargetsInPackageJson(packageJsonPath, options.targets)
381
+ }
138
382
 
139
- function generatePackageJson(options: NewOptions): Output {
140
- return {
141
- target: './package.json',
142
- content: createPackageJson({
143
- name: options.name,
144
- binaryName: getBinaryName(options.name),
145
- targets: options.targets,
146
- license: options.license,
147
- engineRequirement: napiEngineRequirement(options.minNodeApiVersion),
148
- cliVersion: CLI_VERSION,
149
- }),
150
- }
151
- }
383
+ // Filter targets in GitHub Actions CI
384
+ const ciPath = path.join(options.path, '.github', 'workflows', 'CI.yml')
385
+ if (existsSync(ciPath) && options.enableGithubActions) {
386
+ await filterTargetsInGithubActions(ciPath, options.targets)
387
+ } else if (
388
+ !options.enableGithubActions &&
389
+ existsSync(path.join(options.path, '.github'))
390
+ ) {
391
+ // Remove .github directory if GitHub Actions is not enabled
392
+ await fs.rm(path.join(options.path, '.github'), {
393
+ recursive: true,
394
+ force: true,
395
+ })
396
+ }
152
397
 
153
- function generateGithubWorkflow(options: NewOptions): Output | null {
154
- if (!options.enableGithubActions) {
155
- return null
156
- }
398
+ // Update package.json with additional configurations
399
+ const pkgJsonContent = await fs.readFile(packageJsonPath, 'utf-8')
400
+ const pkgJson = JSON.parse(pkgJsonContent)
401
+
402
+ // Update engine requirement
403
+ if (!pkgJson.engines) {
404
+ pkgJson.engines = {}
405
+ }
406
+ pkgJson.engines.node = napiEngineRequirement(options.minNodeApiVersion)
157
407
 
158
- return {
159
- target: './.github/workflows/ci.yml',
160
- content: createGithubActionsCIYml(
161
- getBinaryName(options.name),
162
- options.targets,
163
- ),
408
+ // Update license if different from template
409
+ if (options.license && pkgJson.license !== options.license) {
410
+ pkgJson.license = options.license
411
+ }
412
+
413
+ // Update test framework if needed
414
+ if (options.testFramework !== 'ava') {
415
+ // This would require more complex logic to update test scripts and dependencies
416
+ debug(
417
+ `Test framework ${options.testFramework} requested but not yet implemented`,
418
+ )
419
+ }
420
+
421
+ await fs.writeFile(
422
+ packageJsonPath,
423
+ JSON.stringify(pkgJson, null, 2) + '\n',
424
+ )
425
+ } catch (error) {
426
+ throw new Error(`Failed to create project: ${error}`)
427
+ }
164
428
  }
165
- }
166
429
 
167
- function generateIgnoreFiles(_options: NewOptions): Output[] {
168
- return [
169
- {
170
- target: './.gitignore',
171
- content: gitIgnore,
172
- },
173
- {
174
- target: './.npmignore',
175
- content: npmIgnore,
176
- },
177
- ]
430
+ debug(`Project created at: ${options.path}`)
178
431
  }
179
432
 
180
- async function dumpOutputs(outputs: Output[], dryRun?: boolean) {
181
- for (const output of outputs) {
182
- if (!output) {
183
- continue
184
- }
433
+ async function ensurePath(path: string, dryRun = false) {
434
+ const stat = await statAsync(path, {}).catch(() => undefined)
185
435
 
186
- debug(`Writing project file: ${output.target}`)
187
- // only output content to logger instead of writing to file system
188
- if (dryRun) {
189
- debug(output.content)
190
- continue
436
+ // file descriptor exists
437
+ if (stat) {
438
+ if (stat.isFile()) {
439
+ throw new Error(
440
+ `Path ${path} for creating new napi-rs project already exists and it's not a directory.`,
441
+ )
442
+ } else if (stat.isDirectory()) {
443
+ const files = await readdirAsync(path)
444
+ if (files.length) {
445
+ throw new Error(
446
+ `Path ${path} for creating new napi-rs project already exists and it's not empty.`,
447
+ )
448
+ }
191
449
  }
450
+ }
192
451
 
452
+ if (!dryRun) {
193
453
  try {
194
- await mkdirAsync(path.dirname(output.target), { recursive: true })
195
- await writeFileAsync(output.target, output.content, 'utf-8')
454
+ debug(`Try to create target directory: ${path}`)
455
+ if (!dryRun) {
456
+ await mkdirAsync(path, { recursive: true })
457
+ }
196
458
  } catch (e) {
197
- throw new Error(`Failed to write file: ${output.target}`, { cause: e })
459
+ throw new Error(`Failed to create target directory: ${path}`, {
460
+ cause: e,
461
+ })
198
462
  }
199
463
  }
200
464
  }
@@ -1,6 +1,6 @@
1
- import { execSync } from 'child_process'
2
- import { existsSync, statSync } from 'fs'
3
- import { join, relative, resolve } from 'path'
1
+ import { execSync } from 'node:child_process'
2
+ import { existsSync, statSync } from 'node:fs'
3
+ import { join, resolve } from 'node:path'
4
4
 
5
5
  import { Octokit } from '@octokit/rest'
6
6
 
@@ -31,10 +31,13 @@ export async function prePublish(userOptions: PrePublishOptions) {
31
31
 
32
32
  const options = applyDefaultPrePublishOptions(userOptions)
33
33
 
34
- const packageJsonPath = relative(options.cwd, options.packageJsonPath)
34
+ const packageJsonPath = resolve(options.cwd, options.packageJsonPath)
35
35
 
36
36
  const { packageJson, targets, packageName, binaryName, npmClient } =
37
- await readNapiConfig(packageJsonPath)
37
+ await readNapiConfig(
38
+ packageJsonPath,
39
+ options.configPath ? resolve(options.cwd, options.configPath) : undefined,
40
+ )
38
41
 
39
42
  async function createGhRelease(packageName: string, version: string) {
40
43
  if (!options.ghRelease) {
@@ -129,11 +132,14 @@ export async function prePublish(userOptions: PrePublishOptions) {
129
132
  if (!options.dryRun) {
130
133
  await version(userOptions)
131
134
  await updatePackageJson(packageJsonPath, {
132
- optionalDependencies: targets.reduce((deps, target) => {
133
- deps[`${packageName}-${target.platformArchABI}`] = packageJson.version
134
-
135
- return deps
136
- }, {} as Record<string, string>),
135
+ optionalDependencies: targets.reduce(
136
+ (deps, target) => {
137
+ deps[`${packageName}-${target.platformArchABI}`] = packageJson.version
138
+
139
+ return deps
140
+ },
141
+ {} as Record<string, string>,
142
+ ),
137
143
  })
138
144
  }
139
145
 
@@ -147,7 +153,9 @@ export async function prePublish(userOptions: PrePublishOptions) {
147
153
  options.npmDir,
148
154
  `${target.platformArchABI}`,
149
155
  )
150
- const filename = `${binaryName}.${target.platformArchABI}.node`
156
+ const ext =
157
+ target.platform === 'wasi' || target.platform === 'wasm' ? 'wasm' : 'node'
158
+ const filename = `${binaryName}.${target.platformArchABI}.${ext}`
151
159
  const dstPath = join(pkgDir, filename)
152
160
 
153
161
  if (!options.dryRun) {
@@ -156,10 +164,12 @@ export async function prePublish(userOptions: PrePublishOptions) {
156
164
  continue
157
165
  }
158
166
 
159
- execSync(`${npmClient} publish`, {
160
- cwd: pkgDir,
161
- env: process.env,
162
- })
167
+ if (!options.skipOptionalPublish) {
168
+ execSync(`${npmClient} publish`, {
169
+ cwd: pkgDir,
170
+ env: process.env,
171
+ })
172
+ }
163
173
 
164
174
  if (options.ghRelease && repo && owner) {
165
175
  debug.info(`Creating GitHub release ${pkgInfo.tag}`)
@@ -184,7 +194,8 @@ export async function prePublish(userOptions: PrePublishOptions) {
184
194
  'content-length': dstFileStats.size,
185
195
  'content-type': 'application/octet-stream',
186
196
  },
187
- data: await readFileAsync(dstPath, { encoding: 'utf-8' }),
197
+ // @ts-expect-error octokit types are wrong
198
+ data: await readFileAsync(dstPath),
188
199
  })
189
200
  debug.info(`GitHub release created`)
190
201
  debug.info(`Download URL: %s`, assetInfo.data.browser_download_url)