@tinycld/bootstrap 1.0.1 → 1.1.1
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/bootstrap-tooling.js +139 -30
- package/dist/bootstrap-tooling.js.map +1 -1
- package/dist/index.js +55 -21
- package/dist/index.js.map +1 -1
- package/dist/layout.js +7 -20
- package/dist/layout.js.map +1 -1
- package/dist/link-package.js +78 -62
- package/dist/link-package.js.map +1 -1
- package/dist/prompts.js +3 -3
- package/dist/prompts.js.map +1 -1
- package/package.json +1 -1
- package/templates/full/package.json +7 -1
- package/templates/full/playwright.config.ts +20 -0
- package/templates/full/server/go.mod +2 -2
- package/templates/settings-only/package.json +6 -1
- package/templates/shared/.github/workflows/ci.yml +62 -38
- package/templates/shared/README.md +40 -29
- package/templates/shared/tsconfig.json +5 -14
- package/templates/shared/vitest.config.ts +16 -0
- package/templates/workspace/.go-version +1 -0
- package/templates/workspace/.node-version +1 -0
- package/templates/workspace/tests/expo-clipboard-stub.ts +20 -0
- package/templates/workspace/tests/expo-router-stub.ts +48 -0
- package/templates/workspace/tests/lucide-react-native-stub.cjs +73 -0
- package/templates/workspace/tests/tinycld.packages.test.ts +54 -0
- package/templates/workspace/tests/unit-setup.ts +55 -0
- package/templates/workspace/tinycld.packages.ts +75 -0
- package/templates/workspace/vitest.config.ts +104 -0
|
@@ -1,65 +1,174 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, relative } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
4
5
|
// All feature members that exist in the ecosystem. The workspace manifest lists
|
|
5
6
|
// ALL of them (npm tolerates absent dirs), so a partial checkout still installs.
|
|
6
7
|
// But --tooling CLONES only app+core+requested — we do NOT force-clone all.
|
|
7
8
|
const ALL_FEATURES = ['contacts', 'mail', 'calendar', 'drive', 'calc', 'text', 'google-takeout-import'];
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
9
|
+
// app + core are always cloned by tooling mode (the minimum viable workspace);
|
|
10
|
+
// the clone set in bootstrapTooling seeds them directly (pinnable via
|
|
11
|
+
// appRef/coreRef). package-scripts (the tinycld-pkg CLI) now lives INSIDE the
|
|
12
|
+
// app member at app/package-scripts, so it arrives with the app clone and is
|
|
13
|
+
// never cloned separately.
|
|
14
|
+
// Listed in the manifest workspaces array (everything that could exist). The
|
|
15
|
+
// nested member path app/package-scripts is a valid npm workspace entry.
|
|
16
|
+
const ALL_MEMBERS = ['app', 'app/package-scripts', 'core', ...ALL_FEATURES];
|
|
13
17
|
/**
|
|
14
|
-
* Write a workspace-root package.json + .npmrc
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
+
* Write (or merge into) a workspace-root package.json + .npmrc in `dir`.
|
|
19
|
+
*
|
|
20
|
+
* When `dir/package.json` already exists (e.g. provided by the workspace clone),
|
|
21
|
+
* we preserve its fields and only enforce the two things bootstrap owns:
|
|
22
|
+
* the complete `workspaces` list (so later --with clones need no manifest edit)
|
|
23
|
+
* and the `postinstall` script. All other fields — devDependencies, engines,
|
|
24
|
+
* volta, extra scripts, etc. — survive as-is.
|
|
25
|
+
*
|
|
26
|
+
* When no package.json exists yet (workspace clone was skipped), the full
|
|
27
|
+
* generated defaults are written, matching the previous behaviour.
|
|
28
|
+
*
|
|
29
|
+
* The workspaces array ALWAYS lists every possible member — npm ignores entries
|
|
30
|
+
* whose dirs are absent, so a partial checkout installs fine.
|
|
31
|
+
*
|
|
32
|
+
* .npmrc is written only when one does not already exist, for the same reason.
|
|
18
33
|
*/
|
|
19
34
|
export function writeWorkspaceManifest(dir) {
|
|
35
|
+
const pkgPath = join(dir, 'package.json');
|
|
36
|
+
let existing = {};
|
|
37
|
+
if (existsSync(pkgPath)) {
|
|
38
|
+
try {
|
|
39
|
+
existing = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Unparseable existing file — start from defaults
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const existingScripts = typeof existing.scripts === 'object' && existing.scripts !== null
|
|
46
|
+
? existing.scripts
|
|
47
|
+
: {};
|
|
20
48
|
const pkg = {
|
|
21
49
|
name: '@tinycld/workspace',
|
|
22
50
|
version: '0.0.0',
|
|
23
51
|
private: true,
|
|
24
52
|
type: 'module',
|
|
53
|
+
...existing,
|
|
54
|
+
// Always enforce the canonical workspaces list and postinstall script.
|
|
25
55
|
workspaces: [...ALL_MEMBERS],
|
|
26
56
|
scripts: {
|
|
57
|
+
...existingScripts,
|
|
27
58
|
postinstall: 'cd app && npm run packages:generate && npm run assets:copy-pdfjs',
|
|
28
59
|
},
|
|
29
60
|
};
|
|
30
|
-
writeFileSync(
|
|
31
|
-
|
|
61
|
+
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 4)}\n`);
|
|
62
|
+
const npmrcPath = join(dir, '.npmrc');
|
|
63
|
+
if (!existsSync(npmrcPath)) {
|
|
64
|
+
writeFileSync(npmrcPath, 'legacy-peer-deps=true\n');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the workspace-template dir relative to this module. Published builds
|
|
69
|
+
* have `dist/bootstrap-tooling.js` next to `templates/`; dev has
|
|
70
|
+
* `src/bootstrap-tooling.ts` under the same parent. Both → `../templates/workspace`.
|
|
71
|
+
*/
|
|
72
|
+
function resolveWorkspaceTemplateDir() {
|
|
73
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
74
|
+
return join(here, '..', 'templates', 'workspace');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Copy the workspace-root scaffolding (tinycld.packages.ts, vitest.config.ts,
|
|
78
|
+
* tests/ stubs) from bootstrap's templates/workspace/ into `dir`. These files
|
|
79
|
+
* are pure scaffolding the workspace ROOT needs but that no longer lives in a
|
|
80
|
+
* committed workspace repo — bootstrap is their source of truth.
|
|
81
|
+
*
|
|
82
|
+
* Never overwrites an existing file: a real workspace checkout, or a CI lane
|
|
83
|
+
* that supplied its own copy, keeps what it has (same non-destructive ethos as
|
|
84
|
+
* the member clones). Returns the relative paths actually written.
|
|
85
|
+
*/
|
|
86
|
+
export function copyWorkspaceTemplate(dir, templateDir = resolveWorkspaceTemplateDir()) {
|
|
87
|
+
if (!existsSync(templateDir))
|
|
88
|
+
return [];
|
|
89
|
+
const written = [];
|
|
90
|
+
const walk = (src) => {
|
|
91
|
+
for (const entry of readdirSync(src)) {
|
|
92
|
+
const srcPath = join(src, entry);
|
|
93
|
+
const rel = relative(templateDir, srcPath);
|
|
94
|
+
const dstPath = join(dir, rel);
|
|
95
|
+
if (statSync(srcPath).isDirectory()) {
|
|
96
|
+
walk(srcPath);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (existsSync(dstPath))
|
|
100
|
+
continue; // never overwrite
|
|
101
|
+
mkdirSync(dirname(dstPath), { recursive: true });
|
|
102
|
+
cpSync(srcPath, dstPath);
|
|
103
|
+
written.push(rel);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
walk(templateDir);
|
|
107
|
+
return written;
|
|
32
108
|
}
|
|
33
|
-
|
|
34
|
-
|
|
109
|
+
/** Split a member spec `name@ref` into its parts. No `@` → no ref (clone HEAD). */
|
|
110
|
+
function splitRef(spec) {
|
|
111
|
+
const at = spec.indexOf('@');
|
|
112
|
+
if (at === -1)
|
|
113
|
+
return { name: spec };
|
|
114
|
+
return { name: spec.slice(0, at), ref: spec.slice(at + 1) || undefined };
|
|
115
|
+
}
|
|
116
|
+
function realClone(url, dest, ref) {
|
|
117
|
+
const args = ['clone', '--depth', '1'];
|
|
118
|
+
if (ref)
|
|
119
|
+
args.push('--branch', ref);
|
|
120
|
+
args.push(url, dest);
|
|
121
|
+
const r = spawnSync('git', args, { stdio: 'inherit' });
|
|
35
122
|
return r.status === 0;
|
|
36
123
|
}
|
|
37
124
|
/**
|
|
38
|
-
* Assemble a workspace skeleton at opts.root: write the
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
125
|
+
* Assemble a workspace skeleton at opts.root: write the canonical root manifest
|
|
126
|
+
* (writeWorkspaceManifest) + lay down the root scaffolding (copyWorkspaceTemplate:
|
|
127
|
+
* tinycld.packages.ts, vitest.config.ts, tests/ stubs, version files), then clone
|
|
128
|
+
* ONLY app + core + the explicitly-requested feature members (skipping any
|
|
129
|
+
* already present — e.g. a CI-checked-out member). There is NO workspace
|
|
130
|
+
* meta-repo clone: bootstrap is the source of all root scaffolding.
|
|
131
|
+
* Unknown member names throw. Returns the members that ended up present. Does
|
|
132
|
+
* NOT run npm install (the caller / CI controls that, since it may want a
|
|
133
|
+
* clean `npm ci`).
|
|
43
134
|
*/
|
|
44
135
|
export function bootstrapTooling(opts) {
|
|
45
|
-
|
|
46
|
-
const
|
|
136
|
+
// Each requested member may be `name` or `name@ref`. Validate the NAME part.
|
|
137
|
+
const requested = (opts.members ?? []).map(splitRef);
|
|
138
|
+
const unknown = requested.filter((m) => !ALL_FEATURES.includes(m.name));
|
|
47
139
|
if (unknown.length > 0) {
|
|
48
|
-
throw new Error(`Unknown feature member(s): ${unknown.join(', ')}. Known: ${ALL_FEATURES.join(', ')}`);
|
|
140
|
+
throw new Error(`Unknown feature member(s): ${unknown.map((m) => m.name).join(', ')}. Known: ${ALL_FEATURES.join(', ')}`);
|
|
49
141
|
}
|
|
50
142
|
const repoBase = opts.repoBase ?? 'git@github.com:tinycld';
|
|
51
143
|
const clone = opts.clone ?? realClone;
|
|
52
|
-
writeWorkspaceManifest(opts.root);
|
|
53
|
-
const toClone = Array.from(new Set([...ALWAYS_CLONE, ...requested]));
|
|
54
144
|
const present = [];
|
|
55
|
-
|
|
56
|
-
|
|
145
|
+
// Write the canonical root manifest (workspaces list + postinstall), then
|
|
146
|
+
// lay down the root scaffolding bootstrap owns (tinycld.packages.ts,
|
|
147
|
+
// vitest.config.ts, tests/ stubs, .node-version, .go-version). Both are
|
|
148
|
+
// non-destructive: anything already present (a real workspace checkout, or
|
|
149
|
+
// a CI lane that supplied its own) is left as-is.
|
|
150
|
+
writeWorkspaceManifest(opts.root);
|
|
151
|
+
copyWorkspaceTemplate(opts.root);
|
|
152
|
+
// Build the clone set keyed by member NAME so a member passed both bare and
|
|
153
|
+
// with an @ref dedupes to one clone (the ref-bearing entry wins). app+core
|
|
154
|
+
// are always cloned and pinnable via appRef/coreRef.
|
|
155
|
+
const refByName = new Map();
|
|
156
|
+
refByName.set('app', opts.appRef);
|
|
157
|
+
refByName.set('core', opts.coreRef);
|
|
158
|
+
for (const { name, ref } of requested) {
|
|
159
|
+
// A later ref overrides an earlier bare entry; a bare entry never clears
|
|
160
|
+
// an existing ref.
|
|
161
|
+
if (ref !== undefined || !refByName.has(name))
|
|
162
|
+
refByName.set(name, ref);
|
|
163
|
+
}
|
|
164
|
+
for (const [name, ref] of refByName) {
|
|
165
|
+
const dest = join(opts.root, name);
|
|
57
166
|
if (existsSync(join(dest, '.git')) || existsSync(join(dest, 'package.json'))) {
|
|
58
|
-
present.push(
|
|
167
|
+
present.push(name);
|
|
59
168
|
continue;
|
|
60
169
|
}
|
|
61
|
-
if (clone(`${repoBase}/${
|
|
62
|
-
present.push(
|
|
170
|
+
if (clone(`${repoBase}/${name}.git`, dest, ref))
|
|
171
|
+
present.push(name);
|
|
63
172
|
}
|
|
64
173
|
return present;
|
|
65
174
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap-tooling.js","sourceRoot":"","sources":["../src/bootstrap-tooling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"bootstrap-tooling.js","sourceRoot":"","sources":["../src/bootstrap-tooling.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
|
-
import { dirname, relative } from 'node:path';
|
|
3
|
+
import { dirname, join, relative } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import { intro, outro } from '@clack/prompts';
|
|
6
6
|
import pc from 'picocolors';
|
|
@@ -12,12 +12,34 @@ import { offerLinkPackage } from "./link-package.js";
|
|
|
12
12
|
import { runPrompts } from "./prompts.js";
|
|
13
13
|
export function runToolingMode(opts) {
|
|
14
14
|
const root = opts.root ?? process.cwd();
|
|
15
|
+
// app + core are always-cloned members, not features, so they can't ride the
|
|
16
|
+
// `members` list through bootstrapTooling's feature validation. Accept them
|
|
17
|
+
// in the SAME `--with name@ref` surface for a uniform pinned invocation
|
|
18
|
+
// (`--with app@v1 --with core@v2 --with mail@v3`) by peeling app@ref/core@ref
|
|
19
|
+
// out of `members` into appRef/coreRef. An explicit opts.appRef/coreRef wins.
|
|
20
|
+
let appRef = opts.appRef;
|
|
21
|
+
let coreRef = opts.coreRef;
|
|
22
|
+
const features = [];
|
|
23
|
+
for (const spec of opts.members ?? []) {
|
|
24
|
+
const at = spec.indexOf('@');
|
|
25
|
+
const name = at === -1 ? spec : spec.slice(0, at);
|
|
26
|
+
const ref = at === -1 ? undefined : spec.slice(at + 1) || undefined;
|
|
27
|
+
if (name === 'app')
|
|
28
|
+
appRef = appRef ?? ref;
|
|
29
|
+
else if (name === 'core')
|
|
30
|
+
coreRef = coreRef ?? ref;
|
|
31
|
+
else
|
|
32
|
+
features.push(spec);
|
|
33
|
+
}
|
|
15
34
|
// Honor TINYCLD_REPO_BASE (CI sets it to an HTTPS base since runners have no
|
|
16
35
|
// SSH key). Listed first so an explicit opts.repoBase still wins via spread;
|
|
17
36
|
// when neither is set, bootstrapTooling falls back to its SSH default.
|
|
18
37
|
const present = bootstrapTooling({
|
|
19
38
|
repoBase: process.env.TINYCLD_REPO_BASE,
|
|
20
39
|
...opts,
|
|
40
|
+
members: features,
|
|
41
|
+
appRef,
|
|
42
|
+
coreRef,
|
|
21
43
|
root,
|
|
22
44
|
});
|
|
23
45
|
// Mandatory members must clone — a missing app/core means a broken workspace.
|
|
@@ -51,31 +73,33 @@ async function main() {
|
|
|
51
73
|
}
|
|
52
74
|
const answers = await runPrompts(args);
|
|
53
75
|
copyTemplate(resolveTemplatesRoot(import.meta.url), answers);
|
|
76
|
+
// The package lives at `<workspaceDir>/<slug>`, so its parent is the
|
|
77
|
+
// workspace dir in both attach (cwd) and bootstrap (wrapper) modes.
|
|
54
78
|
const linked = await offerLinkPackage({
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
slug: answers.slug,
|
|
80
|
+
workspaceDir: dirname(answers.targetDir),
|
|
57
81
|
mode: resolveLinkMode(args),
|
|
58
82
|
});
|
|
59
83
|
const relTarget = relative(process.cwd(), answers.targetDir) || answers.targetDir;
|
|
60
84
|
outro(pc.green(`Scaffolded ${pc.bold(answers.slug)} at ${pc.bold(relTarget)}`));
|
|
61
85
|
// Re-derive the layout from the resolved target so the next-steps output
|
|
62
86
|
// matches whatever path the user actually ended up at (default detection,
|
|
63
|
-
// explicit --target, or interactive override).
|
|
64
|
-
//
|
|
87
|
+
// explicit --target, or interactive override). It decides whether to print
|
|
88
|
+
// the bootstrap "self-contained workspace" note.
|
|
65
89
|
const layout = detectLayoutFromTarget(answers.targetDir, answers.slug);
|
|
66
90
|
printNextSteps({ slug: answers.slug, relTarget, linked, layout });
|
|
67
91
|
}
|
|
68
92
|
function detectLayoutFromTarget(targetDir, slug) {
|
|
69
|
-
// If cwd
|
|
70
|
-
//
|
|
71
|
-
//
|
|
93
|
+
// If cwd is itself a workspace root and target is a direct child of it,
|
|
94
|
+
// we're in attach mode. Otherwise the scaffolder created a wrapper under
|
|
95
|
+
// cwd and the workspace meta-repo gets cloned inside it (bootstrap mode).
|
|
72
96
|
const detected = detectLayout(slug);
|
|
73
97
|
if (detected.mode === 'attach' && targetDir === detected.targetDir)
|
|
74
98
|
return 'attach';
|
|
75
99
|
if (detected.mode === 'bootstrap' && targetDir === detected.targetDir)
|
|
76
100
|
return 'bootstrap';
|
|
77
101
|
// User passed an explicit --target. Infer from the on-disk shape: if the
|
|
78
|
-
// parent of targetDir
|
|
102
|
+
// parent of targetDir is itself a workspace root, treat as attach.
|
|
79
103
|
const parent = dirname(targetDir);
|
|
80
104
|
const looksAttached = detectLayout(slug, parent).mode === 'attach';
|
|
81
105
|
return looksAttached ? 'attach' : 'bootstrap';
|
|
@@ -92,11 +116,15 @@ function resolveLinkMode(args) {
|
|
|
92
116
|
function printNextSteps({ slug, relTarget, linked, layout }) {
|
|
93
117
|
const lines = ['', pc.bold('Next steps:'), ''];
|
|
94
118
|
let step = 1;
|
|
119
|
+
// The workspace root, relative to cwd. In bootstrap mode the package sits at
|
|
120
|
+
// `<wrapper>/<slug>`, so the wrapper (`dirname(relTarget)`) is the workspace
|
|
121
|
+
// root. In attach mode cwd already IS the workspace root, so it's `.`.
|
|
122
|
+
const wsRoot = dirname(relTarget) || '.';
|
|
95
123
|
if (layout === 'bootstrap') {
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// got a self-contained workspace.
|
|
99
|
-
lines.push(pc.dim(` Scaffolded a self-contained workspace
|
|
124
|
+
// The meta-repo was cloned into the wrapper, so the wrapper itself is the
|
|
125
|
+
// workspace root and the new package is a member directory inside it.
|
|
126
|
+
// New users may not realize they just got a self-contained workspace.
|
|
127
|
+
lines.push(pc.dim(` Scaffolded a self-contained tinycld workspace.\n The workspace root is ./${wsRoot}/ and your package is ./${relTarget}/.\n The app member (Expo + PocketBase) is ./${wsRoot}/app/.\n`));
|
|
100
128
|
}
|
|
101
129
|
lines.push(` ${pc.dim(`# ${step++}. Initialize git and push to GitHub`)}`);
|
|
102
130
|
lines.push(` cd ${relTarget}`);
|
|
@@ -106,18 +134,24 @@ function printNextSteps({ slug, relTarget, linked, layout }) {
|
|
|
106
134
|
lines.push(` gh repo create tinycld/${slug} --public --source=. --push`);
|
|
107
135
|
lines.push('');
|
|
108
136
|
if (!linked) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
// Linking is now a workspace-root `npm install`: the package dir already
|
|
138
|
+
// exists; add it to the root package.json `workspaces` if it isn't a
|
|
139
|
+
// member yet, then install — npm symlinks it and the postinstall runs
|
|
140
|
+
// the generator.
|
|
141
|
+
lines.push(` ${pc.dim(`# ${step++}. Link into the workspace (add as a member, then install)`)}`);
|
|
142
|
+
if (wsRoot !== '.')
|
|
143
|
+
lines.push(` cd ${wsRoot}`);
|
|
144
|
+
lines.push(` # ensure "${slug}" is in this package.json's "workspaces" array, then:`);
|
|
145
|
+
lines.push(' npm install');
|
|
112
146
|
lines.push('');
|
|
113
147
|
}
|
|
114
|
-
lines.push(` ${pc.dim(`# ${step++}. Verify (biome + tsc
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
lines.push(' npm run checks');
|
|
148
|
+
lines.push(` ${pc.dim(`# ${step++}. Verify the package (biome + tsc, scoped to this member)`)}`);
|
|
149
|
+
lines.push(` cd ${join(wsRoot, slug)}`);
|
|
150
|
+
lines.push(' npx tinycld-pkg check');
|
|
118
151
|
lines.push('');
|
|
119
152
|
lines.push(` ${pc.dim(`# ${step++}. Run the app (Expo + PocketBase, single-port dev proxy)`)}`);
|
|
120
|
-
lines.push(
|
|
153
|
+
lines.push(` cd ${join(wsRoot, 'app')}`);
|
|
154
|
+
lines.push(' npm run dev');
|
|
121
155
|
lines.push('');
|
|
122
156
|
console.log(lines.join('\n'));
|
|
123
157
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/layout.js
CHANGED
|
@@ -1,37 +1,24 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
-
const APP_DIR_NAME = 'tinycld';
|
|
4
3
|
export function detectLayout(slug, cwd = process.cwd()) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return {
|
|
8
|
-
mode: 'attach',
|
|
9
|
-
targetDir: resolve(cwd, slug),
|
|
10
|
-
appDir: candidateApp,
|
|
11
|
-
};
|
|
4
|
+
if (looksLikeWorkspaceRoot(cwd)) {
|
|
5
|
+
return { mode: 'attach', targetDir: resolve(cwd, slug), workspaceDir: cwd };
|
|
12
6
|
}
|
|
13
|
-
const wrapperDir = resolve(cwd,
|
|
7
|
+
const wrapperDir = resolve(cwd, `tinycld-${slug}`);
|
|
14
8
|
return {
|
|
15
9
|
mode: 'bootstrap',
|
|
16
10
|
targetDir: join(wrapperDir, slug),
|
|
17
|
-
|
|
11
|
+
workspaceDir: wrapperDir,
|
|
18
12
|
wrapperDir,
|
|
19
13
|
};
|
|
20
14
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
* declaring `name: "tinycld"`. The bare directory check would false-match any
|
|
24
|
-
* unrelated `tinycld` directory; the package.json check is fast (one read of
|
|
25
|
-
* a file we'd be reading anyway) and unambiguous now that the app shell's
|
|
26
|
-
* package.json `name` field has been renamed from "@tinycld/app" to "tinycld".
|
|
27
|
-
*/
|
|
28
|
-
export function looksLikeAppShell(dir) {
|
|
15
|
+
/** A directory is a workspace root when its package.json name is "@tinycld/workspace". */
|
|
16
|
+
export function looksLikeWorkspaceRoot(dir) {
|
|
29
17
|
const pkgPath = join(dir, 'package.json');
|
|
30
18
|
if (!existsSync(pkgPath))
|
|
31
19
|
return false;
|
|
32
20
|
try {
|
|
33
|
-
|
|
34
|
-
return pkg?.name === 'tinycld';
|
|
21
|
+
return JSON.parse(readFileSync(pkgPath, 'utf-8'))?.name === '@tinycld/workspace';
|
|
35
22
|
}
|
|
36
23
|
catch {
|
|
37
24
|
return false;
|
package/dist/layout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAgBzC,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IAClE,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,CAAA;IAC/E,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,WAAW,IAAI,EAAE,CAAC,CAAA;IAClD,OAAO;QACH,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC;QACjC,YAAY,EAAE,UAAU;QACxB,UAAU;KACb,CAAA;AACL,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IACzC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,KAAK,oBAAoB,CAAA;IACpF,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC"}
|
package/dist/link-package.js
CHANGED
|
@@ -1,85 +1,101 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
4
|
import { confirm, isCancel, log, spinner } from '@clack/prompts';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { bootstrapTooling } from "./bootstrap-tooling.js";
|
|
7
|
+
import { looksLikeWorkspaceRoot } from "./layout.js";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
* Add `slug` to `<workspaceDir>/package.json` `workspaces[]` if it isn't already
|
|
10
|
+
* there. Idempotent, and a no-op when there's no package.json yet.
|
|
11
|
+
*/
|
|
12
|
+
export function ensureMember(workspaceDir, slug) {
|
|
13
|
+
const pkgPath = join(workspaceDir, 'package.json');
|
|
14
|
+
if (!existsSync(pkgPath))
|
|
15
|
+
return;
|
|
16
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
pkg.workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : [];
|
|
18
|
+
if (!pkg.workspaces.includes(slug)) {
|
|
19
|
+
pkg.workspaces.push(slug);
|
|
20
|
+
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 4)}\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Offer to link the freshly-scaffolded package into the TinyCld workspace.
|
|
25
|
+
*
|
|
26
|
+
* "Linking" in the workspace layout means: make the package a workspace member
|
|
27
|
+
* (its dir name appears in the root `package.json` `workspaces[]`) and run
|
|
28
|
+
* `npm install` at the workspace root — npm creates the `node_modules/@tinycld/*`
|
|
29
|
+
* symlink and the root `postinstall` runs the generator.
|
|
30
|
+
*
|
|
31
|
+
* Two shapes, distinguished by whether `workspaceDir` is already a workspace root:
|
|
32
|
+
* - attach: `workspaceDir` is already an assembled workspace root (its
|
|
33
|
+
* package.json name is `@tinycld/workspace`). No assembly — just ensure the
|
|
34
|
+
* member and install. The existing root package.json is left untouched.
|
|
35
|
+
* - bootstrap: `workspaceDir` is a fresh wrapper that only contains the
|
|
36
|
+
* scaffolded `<slug>/` so far. Assemble the workspace *around* it —
|
|
37
|
+
* `bootstrapTooling` generates the root scaffolding and clones app + core
|
|
38
|
+
* (no workspace meta-repo) — then ensure the new member and install. The
|
|
39
|
+
* `postinstall` (`cd app && …`) only succeeds once `app/` exists, which is
|
|
40
|
+
* why we assemble before installing.
|
|
13
41
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* server's `replace tinycld.org/core => ../../tinycld/packages/@tinycld/core/server`
|
|
19
|
-
* directive both resolve into the bundled core inside the app shell.
|
|
42
|
+
* Returns true if the user chose to link (even if a subprocess failed — the
|
|
43
|
+
* error is already logged, and we don't want to follow up with manual steps
|
|
44
|
+
* when the user already expressed intent). Returns false only when linking was
|
|
45
|
+
* declined or skipped.
|
|
20
46
|
*/
|
|
21
|
-
export async function offerLinkPackage({
|
|
47
|
+
export async function offerLinkPackage({ slug, workspaceDir, mode, assemble = realAssemble, install = realInstall, }) {
|
|
22
48
|
if (mode === 'skip')
|
|
23
49
|
return false;
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const appExists = existsSync(appDir);
|
|
50
|
+
// A wrapper that isn't yet a workspace root needs app + core assembled in.
|
|
51
|
+
const needsClone = !looksLikeWorkspaceRoot(workspaceDir);
|
|
27
52
|
if (mode === 'prompt') {
|
|
28
|
-
const
|
|
29
|
-
? `
|
|
30
|
-
: `
|
|
31
|
-
const answer = await confirm({ message
|
|
53
|
+
const message = needsClone
|
|
54
|
+
? `Assemble the tinycld workspace (app + core) and link ${pc.bold(slug)} now?`
|
|
55
|
+
: `Link ${pc.bold(slug)} into the workspace now?`;
|
|
56
|
+
const answer = await confirm({ message, initialValue: true });
|
|
32
57
|
if (isCancel(answer) || answer !== true)
|
|
33
58
|
return false;
|
|
34
59
|
}
|
|
35
|
-
if (
|
|
36
|
-
|
|
60
|
+
if (needsClone) {
|
|
61
|
+
const s = spinner();
|
|
62
|
+
s.start('Assembling the tinycld workspace (app + core)');
|
|
63
|
+
try {
|
|
64
|
+
assemble(workspaceDir);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
s.stop(pc.red('Workspace assembly failed'), 1);
|
|
68
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
37
69
|
return true;
|
|
70
|
+
}
|
|
71
|
+
s.stop('Assembled the tinycld workspace');
|
|
38
72
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const inst = spawnSync('npm', ['install'], { cwd: appDir, stdio: 'pipe', encoding: 'utf8' });
|
|
45
|
-
if (inst.status !== 0) {
|
|
46
|
-
install.stop(pc.red('npm install failed'), 1);
|
|
47
|
-
log.error(inst.stderr.trim() || 'npm install exited non-zero');
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
install.stop(`Installed ${APP_DIR_NAME} dependencies`);
|
|
51
|
-
// packages:link in tinycld takes a sibling-directory locator and reads
|
|
52
|
-
// the package name from the sibling's package.json. Pass a path relative
|
|
53
|
-
// to the app shell so the symlink target stays portable.
|
|
54
|
-
const link = spinner();
|
|
55
|
-
link.start(`Linking ${packageName}`);
|
|
56
|
-
const locator = relative(appDir, targetDir) || targetDir;
|
|
57
|
-
const linkResult = spawnSync('npm', ['run', 'packages:link', locator], {
|
|
58
|
-
cwd: appDir,
|
|
59
|
-
stdio: 'pipe',
|
|
60
|
-
encoding: 'utf8',
|
|
61
|
-
});
|
|
62
|
-
if (linkResult.status !== 0) {
|
|
63
|
-
link.stop(pc.red('Link failed'), 1);
|
|
64
|
-
log.error(linkResult.stderr.trim() || 'packages:link exited non-zero');
|
|
73
|
+
ensureMember(workspaceDir, slug);
|
|
74
|
+
const i = spinner();
|
|
75
|
+
i.start('Installing workspace (npm install)');
|
|
76
|
+
if (!install(workspaceDir)) {
|
|
77
|
+
i.stop(pc.red('npm install failed'), 1);
|
|
65
78
|
return true;
|
|
66
79
|
}
|
|
67
|
-
|
|
80
|
+
i.stop('Installed workspace');
|
|
81
|
+
log.success(`Linked ${pc.bold(slug)} into the workspace`);
|
|
68
82
|
return true;
|
|
69
83
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Assemble the workspace skeleton at `dir`: write the workspace manifest and
|
|
86
|
+
* clone app + core (honoring `TINYCLD_REPO_BASE` for keyless/HTTPS envs).
|
|
87
|
+
* `bootstrapTooling` is synchronous and throws on failure (unknown member, etc.);
|
|
88
|
+
* `offerLinkPackage` wraps the call so the error surfaces in the spinner.
|
|
89
|
+
*/
|
|
90
|
+
function realAssemble(dir) {
|
|
91
|
+
bootstrapTooling({ root: dir, repoBase: process.env.TINYCLD_REPO_BASE });
|
|
92
|
+
}
|
|
93
|
+
function realInstall(cwd) {
|
|
94
|
+
const r = spawnSync('npm', ['install'], { cwd, stdio: 'pipe', encoding: 'utf8' });
|
|
95
|
+
if (r.status !== 0) {
|
|
96
|
+
log.error(r.stderr?.trim() || 'npm install exited non-zero');
|
|
80
97
|
return false;
|
|
81
98
|
}
|
|
82
|
-
s.stop(`Cloned ${dest}`);
|
|
83
99
|
return true;
|
|
84
100
|
}
|
|
85
101
|
//# sourceMappingURL=link-package.js.map
|