@rtorcato/repo-tooling 3.31.1 → 3.32.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/dist/cli/commands/doctor.js +4 -2
- package/dist/cli/generators/claude-skills.js +1 -41
- package/dist/cli/generators/index.js +8 -1
- package/dist/cli/generators/package-json.js +28 -4
- package/dist/cli/generators/pnpm-workspace.js +7 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/utils/lockfile.js +19 -3
- package/dist/cli/utils/version.js +68 -0
- package/dist/languages/swift/scaffold.js +27 -6
- package/package.json +1 -1
|
@@ -232,9 +232,11 @@ async function runBaseChecks(dir, lock, opts) {
|
|
|
232
232
|
results.push(await checkAiSetup(dir));
|
|
233
233
|
// User-global, not repo state — see checkClaudeSkills on why it never returns drift.
|
|
234
234
|
results.push(await checkClaudeSkills(opts.skillsDir));
|
|
235
|
-
// #533: gated on `aiLoop`, which is
|
|
235
|
+
// #533: gated on `aiLoop.agentUser`, which is the "this repo uses the pipeline"
|
|
236
236
|
// signal, so a repo that doesn't gets no line at all rather than an empty one.
|
|
237
|
-
|
|
237
|
+
// The key itself no longer says that — since #571 every repo is scaffolded with
|
|
238
|
+
// an empty `aiLoop`, and the skills have always read the login, not the key.
|
|
239
|
+
if (lock?.rules?.aiLoop?.agentUser && lock.rules.requiredSkills?.length) {
|
|
238
240
|
results.push(await checkRequiredSkills(lock.rules.requiredSkills, opts.skillsDir));
|
|
239
241
|
}
|
|
240
242
|
// #534: advisory. Absent `mcp.recommended` means the repo has nothing to say
|
|
@@ -11,6 +11,7 @@ import fs from 'fs-extra';
|
|
|
11
11
|
import { realGitExec } from '../../base/git-identity.js';
|
|
12
12
|
import { getPackageRoot } from '../utils/copy-preset.js';
|
|
13
13
|
import { shellQuote } from '../utils/shell.js';
|
|
14
|
+
import { isNewerVersion, resolveShippedVersion } from '../utils/version.js';
|
|
14
15
|
/**
|
|
15
16
|
* Skills this package owns the content of and keeps up to date. The loop first —
|
|
16
17
|
* it is the pipeline; the other three are its drivers (burst, on-ramp, status).
|
|
@@ -115,47 +116,6 @@ export function classifySkillContent(installed, shipped) {
|
|
|
115
116
|
return 'unknown';
|
|
116
117
|
return recorded === hashSkillContent(installed) ? 'pristine' : 'modified';
|
|
117
118
|
}
|
|
118
|
-
function versionParts(version) {
|
|
119
|
-
return version.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
|
120
|
-
}
|
|
121
|
-
/** True when `a` is a strictly higher version than `b`. Prerelease tags are ignored. */
|
|
122
|
-
export function isNewerVersion(a, b) {
|
|
123
|
-
const [left, right] = [versionParts(a), versionParts(b)];
|
|
124
|
-
for (let i = 0; i < 3; i++) {
|
|
125
|
-
const [x, y] = [left[i] ?? 0, right[i] ?? 0];
|
|
126
|
-
if (x !== y)
|
|
127
|
-
return x > y;
|
|
128
|
-
}
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* The version to stamp, given what `package.json` claims.
|
|
133
|
-
*
|
|
134
|
-
* In a published tarball that field is authoritative — `@semantic-release/npm`
|
|
135
|
-
* rewrites it before packing. In a *git checkout* it is not: this repo runs
|
|
136
|
-
* semantic-release without `@semantic-release/git` (#417), so nothing ever
|
|
137
|
-
* writes the released version back and the field sits at whatever it was last
|
|
138
|
-
* hand-set to. Observed 2026-08-22: `package.json` 3.11.0 against npm 3.21.1,
|
|
139
|
-
* ten minor versions of drift.
|
|
140
|
-
*
|
|
141
|
-
* That mattered because the stamp feeds the downgrade guard below: a skill
|
|
142
|
-
* installed from npm could not be updated from a local checkout, and the
|
|
143
|
-
* refusal claimed the installed copy was "newer" when only its *label* was.
|
|
144
|
-
*
|
|
145
|
-
* So when the package root is a git checkout, take the nearest tag as well and
|
|
146
|
-
* keep whichever is higher. Monotonic on purpose — this can only ever raise the
|
|
147
|
-
* answer, so a tagless, shallow, or git-less environment keeps today's
|
|
148
|
-
* behaviour rather than silently stamping something lower.
|
|
149
|
-
*/
|
|
150
|
-
export async function resolveShippedVersion(root, pkgVersion, git = realGitExec) {
|
|
151
|
-
if (!(await fs.pathExists(path.join(root, '.git'))))
|
|
152
|
-
return pkgVersion;
|
|
153
|
-
const described = await git(['describe', '--tags', '--abbrev=0'], root);
|
|
154
|
-
const tag = described?.trim().replace(/^v/, '');
|
|
155
|
-
if (!tag || !/^\d+\.\d+/.test(tag))
|
|
156
|
-
return pkgVersion;
|
|
157
|
-
return isNewerVersion(tag, pkgVersion) ? tag : pkgVersion;
|
|
158
|
-
}
|
|
159
119
|
/** The skill source and the package version that will be stamped into it. */
|
|
160
120
|
export async function readShippedSkill(name = SHIPPED_SKILL, git = realGitExec) {
|
|
161
121
|
const root = getPackageRoot();
|
|
@@ -91,7 +91,14 @@ export async function generateConfigs(config, targetDir) {
|
|
|
91
91
|
await ensureBuildApprovals(config, targetDir);
|
|
92
92
|
// Family-wide pnpm settings (#314). Runs last of the workspace writers so it
|
|
93
93
|
// merges into whatever they wrote rather than racing them for the file.
|
|
94
|
-
|
|
94
|
+
//
|
|
95
|
+
// The scope is read back off the emitted package.json rather than taken from
|
|
96
|
+
// config.projectName: under `--preset` that name is only the directory
|
|
97
|
+
// basename, while generatePackageJson preserves an existing scoped `name`.
|
|
98
|
+
// doctor derives the glob from that same field, so reading the basename left
|
|
99
|
+
// the two disagreeing on every scoped repo (#573).
|
|
100
|
+
const { name } = (await fs.readJson(path.join(targetDir, 'package.json')));
|
|
101
|
+
await ensurePnpmSettings(targetDir, bundlerNeedsEsbuild(config), familyGlob(name));
|
|
95
102
|
// Turborepo task pipeline (pnpm-workspace monorepos, when opted-in)
|
|
96
103
|
if (config.turborepo) {
|
|
97
104
|
await generateTurborepo(targetDir);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import fs from 'fs-extra';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { PNPM_FALLBACK_VERSION, detectPnpmVersion } from './misc.js';
|
|
@@ -8,6 +9,8 @@ export async function generatePackageJson(config, targetDir) {
|
|
|
8
9
|
existingPackageJson = await fs.readJson(packageJsonPath);
|
|
9
10
|
}
|
|
10
11
|
const includeTreeshake = Boolean(config.treeshakeCheck && config.projectType === 'library');
|
|
12
|
+
const generatedScripts = getScripts(config, { includeTreeshake });
|
|
13
|
+
const existingScripts = existingPackageJson?.scripts ?? {};
|
|
11
14
|
const packageJson = {
|
|
12
15
|
name: config.projectName,
|
|
13
16
|
version: '0.1.0',
|
|
@@ -17,10 +20,7 @@ export async function generatePackageJson(config, targetDir) {
|
|
|
17
20
|
// generated workflows carry no `version:` input (#364).
|
|
18
21
|
packageManager: `pnpm@${detectPnpmVersion() ?? PNPM_FALLBACK_VERSION}`,
|
|
19
22
|
...existingPackageJson,
|
|
20
|
-
scripts:
|
|
21
|
-
...getScripts(config, { includeTreeshake }),
|
|
22
|
-
...existingPackageJson?.scripts,
|
|
23
|
-
},
|
|
23
|
+
scripts: resolveScripts(config, generatedScripts, existingScripts),
|
|
24
24
|
dependencies: {
|
|
25
25
|
...existingPackageJson?.dependencies,
|
|
26
26
|
},
|
|
@@ -66,6 +66,30 @@ export async function generatePackageJson(config, targetDir) {
|
|
|
66
66
|
// (see ensureBuildApprovals in build.ts).
|
|
67
67
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Merge the generated scripts with what the package.json already had. Existing
|
|
71
|
+
* values win — that merge-don't-clobber policy is what makes re-running `setup`
|
|
72
|
+
* on a live repo safe.
|
|
73
|
+
*
|
|
74
|
+
* `build` on a library is the one exception. The library branch of
|
|
75
|
+
* generatePackageJson overwrites the whole publish contract
|
|
76
|
+
* (main/module/types/exports) unconditionally, and that contract names files
|
|
77
|
+
* only the preset's bundler emits: a preserved `build: tsc` produces
|
|
78
|
+
* dist/index.js + dist/index.d.ts and never the ./dist/index.cjs that `main`
|
|
79
|
+
* and every `require` condition point at, so `pnpm build && npm publish` ships
|
|
80
|
+
* a package whose CJS entry points 404 (#570). Owning the contract means owning
|
|
81
|
+
* its producer; the replacement is announced rather than silent.
|
|
82
|
+
*/
|
|
83
|
+
function resolveScripts(config, generated, existing) {
|
|
84
|
+
const scripts = { ...generated, ...existing };
|
|
85
|
+
if (config.projectType !== 'library' || !generated.build)
|
|
86
|
+
return scripts;
|
|
87
|
+
if (existing.build && existing.build !== generated.build) {
|
|
88
|
+
console.warn(chalk.yellow(`⚠️ Replaced "build": "${existing.build}" with "${generated.build}" — the library exports map names files only ${config.bundler} emits.`));
|
|
89
|
+
}
|
|
90
|
+
scripts.build = generated.build;
|
|
91
|
+
return scripts;
|
|
92
|
+
}
|
|
69
93
|
function getScripts(config, opts = {}) {
|
|
70
94
|
const scripts = {};
|
|
71
95
|
// TypeScript scripts
|
|
@@ -24,10 +24,16 @@ export const WORKSPACE_FILE = 'pnpm-workspace.yaml';
|
|
|
24
24
|
*
|
|
25
25
|
* One glob covers a whole scope: pnpm matches these entries with
|
|
26
26
|
* `@pnpm/config.matcher`, so there's no package list to keep in sync.
|
|
27
|
+
*
|
|
28
|
+
* The scope is matched against npm's own name charset, not just "anything up
|
|
29
|
+
* to the slash": the name comes verbatim from a pre-existing `package.json`
|
|
30
|
+
* and is never validated as an npm name on the way here, so a scope of a lone
|
|
31
|
+
* wildcard would otherwise be taken as a glob and exempt every scoped package
|
|
32
|
+
* from the release-age delay. An unparseable scope gets no setting at all.
|
|
27
33
|
*/
|
|
28
34
|
export function familyGlob(packageName) {
|
|
29
35
|
const name = typeof packageName === 'string' ? packageName : '';
|
|
30
|
-
const scope = /^(@[
|
|
36
|
+
const scope = /^(@[a-z0-9-][a-z0-9._-]*)\//i.exec(name)?.[1];
|
|
31
37
|
return scope ? `${scope}/*` : null;
|
|
32
38
|
}
|
|
33
39
|
/** Bundlers that pull in esbuild, whose install script pnpm 11 refuses to run unapproved. */
|
package/dist/cli/index.js
CHANGED
|
@@ -3,12 +3,12 @@ import path from 'node:path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import fs from 'fs-extra';
|
|
6
|
-
import packageJson from '../../package.json' with { type: 'json' };
|
|
7
6
|
import { doctorCommand } from './commands/doctor.js';
|
|
8
7
|
import { fixCommand } from './commands/fix.js';
|
|
9
8
|
import { loopGuardCommand } from './commands/loop-guard.js';
|
|
10
9
|
import { setupProject } from './commands/setup.js';
|
|
11
10
|
import { copyPreset, PRESETS } from './utils/copy-preset.js';
|
|
11
|
+
import { getToolVersion } from './utils/version.js';
|
|
12
12
|
async function isSelfRepo(dir) {
|
|
13
13
|
try {
|
|
14
14
|
const pkg = await fs.readJson(path.join(dir, 'package.json'));
|
|
@@ -22,7 +22,7 @@ const program = new Command();
|
|
|
22
22
|
program
|
|
23
23
|
.name('@rtorcato/repo-tooling')
|
|
24
24
|
.description('🛠️ JavaScript and TypeScript tooling setup for modern projects')
|
|
25
|
-
.version(
|
|
25
|
+
.version(await getToolVersion());
|
|
26
26
|
program
|
|
27
27
|
.command('setup')
|
|
28
28
|
.alias('init')
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
|
-
import packageJson from '../../../package.json' with { type: 'json' };
|
|
4
3
|
import { CONFIG_SCHEMA, validateProjectConfig } from '../commands/setup-presets.js';
|
|
5
4
|
import { SHIPPED_SKILLS } from '../generators/claude-skills.js';
|
|
5
|
+
import { getToolVersion } from './version.js';
|
|
6
6
|
export const LOCKFILE_NAME = '.repo-tooling.json';
|
|
7
7
|
// Package and bin name used before the js-tooling→repo-tooling rename (#272).
|
|
8
8
|
// The bin no longer exists and the package is 404 on the registry, so any
|
|
@@ -27,6 +27,21 @@ const LOCKFILE_SCHEMA_URL = 'https://rtorcato.github.io/repo-tooling/schemas/loc
|
|
|
27
27
|
* branches on it beyond printing it.
|
|
28
28
|
*/
|
|
29
29
|
export const MCP_IMPORTANCE = ['nice-to-have', 'important', 'critical'];
|
|
30
|
+
/**
|
|
31
|
+
* The empty ruleset a repo with no stated rules gets (#571). `rules` used to
|
|
32
|
+
* appear only via v1–v3 migration, so the repos this tool creates were exactly
|
|
33
|
+
* the ones with nothing to edit and nothing to compare. Writing the containers
|
|
34
|
+
* — with `$schema` stamped alongside — makes the file teach its own shape.
|
|
35
|
+
*
|
|
36
|
+
* Values, never defaults: every entry is empty, because a populated one would
|
|
37
|
+
* be this tool asserting a rule on a repo whose humans have not stated any.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_RULES = {
|
|
40
|
+
aiLoop: {},
|
|
41
|
+
requiredSkills: [],
|
|
42
|
+
mcp: { recommended: [] },
|
|
43
|
+
exceptions: {},
|
|
44
|
+
};
|
|
30
45
|
/**
|
|
31
46
|
* JSON Schema for the lockfile, published with the docs site at the exact URL
|
|
32
47
|
* every written lockfile's `$schema` points to (#529). The `satisfies` clauses
|
|
@@ -243,10 +258,11 @@ export async function writeLockfile(dir, config, assets) {
|
|
|
243
258
|
record: {
|
|
244
259
|
config,
|
|
245
260
|
...(carried && Object.keys(carried).length > 0 ? { assets: carried } : {}),
|
|
246
|
-
writtenBy: `@rtorcato/repo-tooling@${
|
|
261
|
+
writtenBy: `@rtorcato/repo-tooling@${await getToolVersion()}`,
|
|
247
262
|
writtenAt: new Date().toISOString(),
|
|
248
263
|
},
|
|
249
|
-
|
|
264
|
+
// A repo that has stated no rules gets the empty scaffold, not nothing (#571).
|
|
265
|
+
rules: existing?.rules ?? DEFAULT_RULES,
|
|
250
266
|
};
|
|
251
267
|
await fs.writeJson(filepath, lockfile, { spaces: 2 });
|
|
252
268
|
// Migrate a pre-rename repo to the new name: now that the canonical file is
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one answer to "what version of this package is running" (#572).
|
|
3
|
+
*
|
|
4
|
+
* There were two. `--version` and the lockfile's `writtenBy` read the imported
|
|
5
|
+
* `package.json` directly; the Claude-skills stamp went through
|
|
6
|
+
* `resolveShippedVersion`, which corrects for the drift documented below. A
|
|
7
|
+
* single `doctor` run could report both — the skills check saying 3.31.0 while
|
|
8
|
+
* `writtenBy` in the same run said 3.11.0. Everything now calls
|
|
9
|
+
* `getToolVersion()`.
|
|
10
|
+
*/
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import fs from 'fs-extra';
|
|
13
|
+
import packageJson from '../../../package.json' with { type: 'json' };
|
|
14
|
+
import { realGitExec } from '../../base/git-identity.js';
|
|
15
|
+
import { getPackageRoot } from './copy-preset.js';
|
|
16
|
+
function versionParts(version) {
|
|
17
|
+
return version.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
|
18
|
+
}
|
|
19
|
+
/** True when `a` is a strictly higher version than `b`. Prerelease tags are ignored. */
|
|
20
|
+
export function isNewerVersion(a, b) {
|
|
21
|
+
const [left, right] = [versionParts(a), versionParts(b)];
|
|
22
|
+
for (let i = 0; i < 3; i++) {
|
|
23
|
+
const [x, y] = [left[i] ?? 0, right[i] ?? 0];
|
|
24
|
+
if (x !== y)
|
|
25
|
+
return x > y;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The version to report, given what `package.json` claims.
|
|
31
|
+
*
|
|
32
|
+
* In a published tarball that field is authoritative — `@semantic-release/npm`
|
|
33
|
+
* rewrites it before packing. In a *git checkout* it is not: this repo runs
|
|
34
|
+
* semantic-release without `@semantic-release/git` (#417), so nothing ever
|
|
35
|
+
* writes the released version back and the field sits at whatever it was last
|
|
36
|
+
* hand-set to. Observed 2026-08-22: `package.json` 3.11.0 against npm 3.21.1,
|
|
37
|
+
* ten minor versions of drift.
|
|
38
|
+
*
|
|
39
|
+
* That mattered first because the stamp feeds the skills downgrade guard: a
|
|
40
|
+
* skill installed from npm could not be updated from a local checkout, and the
|
|
41
|
+
* refusal claimed the installed copy was "newer" when only its *label* was.
|
|
42
|
+
*
|
|
43
|
+
* So when the package root is a git checkout, take the nearest tag as well and
|
|
44
|
+
* keep whichever is higher. Monotonic on purpose — this can only ever raise the
|
|
45
|
+
* answer, so a tagless, shallow, or git-less environment keeps today's
|
|
46
|
+
* behaviour rather than silently reporting something lower.
|
|
47
|
+
*/
|
|
48
|
+
export async function resolveShippedVersion(root, pkgVersion, git = realGitExec) {
|
|
49
|
+
if (!(await fs.pathExists(path.join(root, '.git'))))
|
|
50
|
+
return pkgVersion;
|
|
51
|
+
const described = await git(['describe', '--tags', '--abbrev=0'], root);
|
|
52
|
+
const tag = described?.trim().replace(/^v/, '');
|
|
53
|
+
if (!tag || !/^\d+\.\d+/.test(tag))
|
|
54
|
+
return pkgVersion;
|
|
55
|
+
return isNewerVersion(tag, pkgVersion) ? tag : pkgVersion;
|
|
56
|
+
}
|
|
57
|
+
let cached;
|
|
58
|
+
/**
|
|
59
|
+
* This package's version, for `--version`, `writtenBy`, and the skill stamps.
|
|
60
|
+
*
|
|
61
|
+
* Memoized: in a checkout `resolveShippedVersion` spawns `git describe`, and
|
|
62
|
+
* nothing about the answer changes mid-process. An npm install has no `.git`
|
|
63
|
+
* under the package root, so that path costs one `pathExists` and no subprocess.
|
|
64
|
+
*/
|
|
65
|
+
export function getToolVersion() {
|
|
66
|
+
cached ??= resolveShippedVersion(getPackageRoot(), String(packageJson.version));
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
@@ -18,7 +18,7 @@ import { generateEditorConfig } from '../../cli/generators/misc.js';
|
|
|
18
18
|
import { generateCodeQLWorkflow, generateDependabotConfig } from '../../cli/generators/security.js';
|
|
19
19
|
import { copyPreset } from '../../cli/utils/copy-preset.js';
|
|
20
20
|
import { LANGUAGES } from '../registry.js';
|
|
21
|
-
import { readSwiftPackage, renderSwiftReleaseWorkflow, renderSwiftWorkflow } from './ci.js';
|
|
21
|
+
import { parsePackageSwift, readSwiftPackage, renderSwiftReleaseWorkflow, renderSwiftWorkflow, } from './ci.js';
|
|
22
22
|
import { SWIFT_HOOKS_DIR, installSwiftGitHooks } from './git-hooks.js';
|
|
23
23
|
import { ensureSwiftGitignore } from './gitignore.js';
|
|
24
24
|
/**
|
|
@@ -196,6 +196,10 @@ git tag 1.0.0 && git push origin 1.0.0
|
|
|
196
196
|
/**
|
|
197
197
|
* Relative paths `setup --dry-run` reports for a Swift config, in write order.
|
|
198
198
|
* Kept beside the writer so the two can't drift.
|
|
199
|
+
*
|
|
200
|
+
* This is the greenfield list. Against a directory that already has a
|
|
201
|
+
* Package.swift the writer preserves it and skips the target scaffolding, so the
|
|
202
|
+
* preview over-reports there — the safe direction for a dry run.
|
|
199
203
|
*/
|
|
200
204
|
export function swiftFileList(config) {
|
|
201
205
|
const module = swiftModuleName(config.projectName);
|
|
@@ -225,12 +229,29 @@ export function swiftFileList(config) {
|
|
|
225
229
|
files.push('README.md');
|
|
226
230
|
return files;
|
|
227
231
|
}
|
|
228
|
-
/**
|
|
232
|
+
/**
|
|
233
|
+
* Scaffold a SwiftPM library. The Swift arm of `generateConfigs`.
|
|
234
|
+
*
|
|
235
|
+
* Package.swift is source, not config (#574): rewriting one that already exists
|
|
236
|
+
* renames the package after the *directory* and leaves the real `Sources/<Target>/`
|
|
237
|
+
* declared by no target — and `swift build` still exits 0, because SwiftPM just
|
|
238
|
+
* ignores the orphaned directory. So an existing manifest is left byte-identical
|
|
239
|
+
* and the module name is read back off it rather than derived from the directory,
|
|
240
|
+
* the same way the JS presets merge into a package.json instead of replacing it.
|
|
241
|
+
*/
|
|
229
242
|
export async function generateSwiftProject(config, targetDir) {
|
|
230
|
-
const
|
|
231
|
-
await fs.
|
|
232
|
-
|
|
233
|
-
|
|
243
|
+
const manifestPath = path.join(targetDir, 'Package.swift');
|
|
244
|
+
const existingManifest = (await fs.pathExists(manifestPath))
|
|
245
|
+
? parsePackageSwift(await fs.readFile(manifestPath, 'utf-8'))
|
|
246
|
+
: null;
|
|
247
|
+
const module = existingManifest?.products[0] ?? swiftModuleName(config.projectName);
|
|
248
|
+
// Nothing is scaffolded under Sources/ or Tests/ either: those would be
|
|
249
|
+
// directories for a target the existing manifest doesn't declare.
|
|
250
|
+
if (!existingManifest) {
|
|
251
|
+
await fs.writeFile(manifestPath, packageSwift(module));
|
|
252
|
+
await fs.outputFile(path.join(targetDir, 'Sources', module, `${module}.swift`), sourceFile(module));
|
|
253
|
+
await fs.outputFile(path.join(targetDir, 'Tests', `${module}Tests`, `${module}Tests.swift`), testFile(module));
|
|
254
|
+
}
|
|
234
255
|
await copyPreset('swiftlint', targetDir);
|
|
235
256
|
await copyPreset('periphery', targetDir);
|
|
236
257
|
await ensureSwiftGitignore(targetDir);
|
package/package.json
CHANGED