dazscript-framework 1.0.30 → 1.0.32
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.
|
@@ -153,11 +153,65 @@ function run(command, args, options) {
|
|
|
153
153
|
return result;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function getNpmInvocation(args, platform) {
|
|
157
|
+
const npmArgs = args || [];
|
|
158
|
+
if ((platform || process.platform) !== 'win32') {
|
|
159
|
+
return { command: 'npm', args: npmArgs };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const npmCliPath = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
163
|
+
if (fs.existsSync(npmCliPath)) {
|
|
164
|
+
return { command: process.execPath, args: [npmCliPath].concat(npmArgs) };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { command: 'npm.cmd', args: npmArgs };
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
function writeFile(filePath, content) {
|
|
157
171
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
158
172
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
159
173
|
}
|
|
160
174
|
|
|
175
|
+
const fixtureBuildDependencies = [
|
|
176
|
+
'@babel/core',
|
|
177
|
+
'@babel/plugin-proposal-class-properties',
|
|
178
|
+
'@babel/plugin-proposal-decorators',
|
|
179
|
+
'@babel/plugin-transform-arrow-functions',
|
|
180
|
+
'@babel/plugin-transform-block-scoping',
|
|
181
|
+
'@babel/plugin-transform-class-properties',
|
|
182
|
+
'@babel/plugin-transform-private-methods',
|
|
183
|
+
'@babel/plugin-transform-private-property-in-object',
|
|
184
|
+
'@babel/preset-env',
|
|
185
|
+
'@babel/preset-typescript',
|
|
186
|
+
'babel-core',
|
|
187
|
+
'babel-loader',
|
|
188
|
+
'babel-plugin-transform-class-properties',
|
|
189
|
+
'babel-plugin-transform-typescript-metadata',
|
|
190
|
+
'glob',
|
|
191
|
+
'ts-loader',
|
|
192
|
+
'tsconfig-paths-webpack-plugin',
|
|
193
|
+
'typescript',
|
|
194
|
+
'webpack',
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
function getFixtureBuildDependencies(frameworkRoot) {
|
|
198
|
+
const frameworkPackageJson = JSON.parse(
|
|
199
|
+
fs.readFileSync(path.join(frameworkRoot, 'package.json'), 'utf8')
|
|
200
|
+
);
|
|
201
|
+
const availableDependencies = {
|
|
202
|
+
...(frameworkPackageJson.dependencies || {}),
|
|
203
|
+
...(frameworkPackageJson.devDependencies || {}),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return fixtureBuildDependencies.reduce((result, dependencyName) => {
|
|
207
|
+
const version = availableDependencies[dependencyName];
|
|
208
|
+
if (version) {
|
|
209
|
+
result[dependencyName] = version;
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}, {});
|
|
213
|
+
}
|
|
214
|
+
|
|
161
215
|
function buildFixtureProject(options) {
|
|
162
216
|
fs.rmSync(options.fixtureRoot, { recursive: true, force: true });
|
|
163
217
|
fs.mkdirSync(path.join(options.fixtureRoot, 'src'), { recursive: true });
|
|
@@ -171,7 +225,7 @@ function buildFixtureProject(options) {
|
|
|
171
225
|
'dazscript-framework': `file:${options.frameworkRoot}`,
|
|
172
226
|
'dazscript-types': '^1.0.1',
|
|
173
227
|
},
|
|
174
|
-
devDependencies:
|
|
228
|
+
devDependencies: getFixtureBuildDependencies(options.frameworkRoot),
|
|
175
229
|
}, null, 2));
|
|
176
230
|
|
|
177
231
|
writeFile(path.join(options.fixtureRoot, 'dazscript.config.ts'), [
|
|
@@ -223,8 +277,10 @@ function buildFixtureProject(options) {
|
|
|
223
277
|
}, null, 2));
|
|
224
278
|
|
|
225
279
|
fs.copyFileSync(options.fixturePath, path.join(options.fixtureRoot, 'src', `${options.fixtureName}.dsa.ts`));
|
|
226
|
-
|
|
227
|
-
run(
|
|
280
|
+
const npmInstall = getNpmInvocation(['install', '--ignore-scripts']);
|
|
281
|
+
run(npmInstall.command, npmInstall.args, { cwd: options.fixtureRoot });
|
|
282
|
+
const npmBuild = getNpmInvocation(['run', 'build']);
|
|
283
|
+
run(npmBuild.command, npmBuild.args, { cwd: options.fixtureRoot });
|
|
228
284
|
}
|
|
229
285
|
|
|
230
286
|
function runDazFixture(options) {
|
|
@@ -296,6 +352,8 @@ async function runIntegration(rawOptions, injectedEnv, cwd) {
|
|
|
296
352
|
module.exports = {
|
|
297
353
|
loadEnvFile,
|
|
298
354
|
normalizeForDaz,
|
|
355
|
+
getNpmInvocation,
|
|
356
|
+
getFixtureBuildDependencies,
|
|
299
357
|
readIntegrationResult,
|
|
300
358
|
resolveIntegrationOptions,
|
|
301
359
|
buildFixtureProject,
|
package/package.json
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const started: Array<{
|
|
4
|
+
info: string
|
|
5
|
+
totalSteps: number
|
|
6
|
+
isCancellable: boolean
|
|
7
|
+
showTimeElapsed: boolean
|
|
8
|
+
}> = []
|
|
9
|
+
const steps: number[] = []
|
|
10
|
+
|
|
11
|
+
const installProgressGlobals = (cancelled = false): void => {
|
|
12
|
+
vi.stubGlobal('startProgress', (info: string, totalSteps: number, isCancellable: boolean, showTimeElapsed: boolean) => {
|
|
13
|
+
started.push({ info, totalSteps, isCancellable, showTimeElapsed })
|
|
14
|
+
})
|
|
15
|
+
vi.stubGlobal('stepProgress', (count = 1) => {
|
|
16
|
+
steps.push(count)
|
|
17
|
+
})
|
|
18
|
+
vi.stubGlobal('finishProgress', vi.fn())
|
|
19
|
+
vi.stubGlobal('progressIsCancelled', () => cancelled)
|
|
20
|
+
vi.stubGlobal('processEvents', vi.fn())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
started.length = 0
|
|
25
|
+
steps.length = 0
|
|
26
|
+
vi.resetModules()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
vi.unstubAllGlobals()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('withProgress', () => {
|
|
34
|
+
it('starts progress, exposes a manual step handle, and finishes after callback returns', async () => {
|
|
35
|
+
installProgressGlobals()
|
|
36
|
+
const { withProgress } = await import('./progress-helper')
|
|
37
|
+
|
|
38
|
+
const result = withProgress('Manual Work', 4, (progress) => {
|
|
39
|
+
progress.step()
|
|
40
|
+
progress.step(2)
|
|
41
|
+
return 'done'
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
expect(result).toBe('done')
|
|
45
|
+
expect(started).toEqual([
|
|
46
|
+
{
|
|
47
|
+
info: 'Manual Work',
|
|
48
|
+
totalSteps: 4,
|
|
49
|
+
isCancellable: true,
|
|
50
|
+
showTimeElapsed: true,
|
|
51
|
+
},
|
|
52
|
+
])
|
|
53
|
+
expect(steps).toEqual([1, 2])
|
|
54
|
+
expect(finishProgress).toHaveBeenCalledTimes(1)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('finishes progress when the callback throws', async () => {
|
|
58
|
+
installProgressGlobals()
|
|
59
|
+
const { withProgress } = await import('./progress-helper')
|
|
60
|
+
|
|
61
|
+
expect(() => withProgress('Manual Work', 2, () => {
|
|
62
|
+
throw new Error('failed phase')
|
|
63
|
+
})).toThrow('failed phase')
|
|
64
|
+
|
|
65
|
+
expect(finishProgress).toHaveBeenCalledTimes(1)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('reports cancellation consistently with cancellable progress', async () => {
|
|
69
|
+
installProgressGlobals(true)
|
|
70
|
+
const { withProgress } = await import('./progress-helper')
|
|
71
|
+
|
|
72
|
+
let callbackCancelled = false
|
|
73
|
+
withProgress('Manual Work', 2, (progress) => {
|
|
74
|
+
callbackCancelled = progress.isCancelled()
|
|
75
|
+
progress.step()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
expect(callbackCancelled).toBe(true)
|
|
79
|
+
expect(processEvents).toHaveBeenCalledTimes(1)
|
|
80
|
+
expect(steps).toEqual([])
|
|
81
|
+
expect(finishProgress).toHaveBeenCalledTimes(1)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('progress', () => {
|
|
86
|
+
it('finishes progress when an item callback stops the loop', async () => {
|
|
87
|
+
installProgressGlobals()
|
|
88
|
+
const { progress } = await import('./progress-helper')
|
|
89
|
+
|
|
90
|
+
progress('Array Work', [1, 2, 3], (item: number) => item !== 2)
|
|
91
|
+
|
|
92
|
+
expect(steps).toEqual([1])
|
|
93
|
+
expect(finishProgress).toHaveBeenCalledTimes(1)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import { status } from '@dsf/common/log';
|
|
2
2
|
|
|
3
|
+
export type ProgressOptions = {
|
|
4
|
+
isCancellable?: boolean
|
|
5
|
+
showTimeElapsed?: boolean
|
|
6
|
+
totalSteps?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ProgressHandle = {
|
|
10
|
+
step: (count?: number) => boolean
|
|
11
|
+
isCancelled: () => boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const defaultProgressOptions = (totalSteps: number, options?: ProgressOptions): Required<ProgressOptions> => ({
|
|
15
|
+
isCancellable: true,
|
|
16
|
+
showTimeElapsed: true,
|
|
17
|
+
totalSteps,
|
|
18
|
+
...options
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const reportInfo = (info: string | string[]): string => {
|
|
22
|
+
const infoMessage = Array.isArray(info) ? '' : info;
|
|
23
|
+
if (Array.isArray(info)) info.forEach(line => status(line));
|
|
24
|
+
return infoMessage;
|
|
25
|
+
}
|
|
26
|
+
|
|
3
27
|
/**
|
|
4
28
|
* Displays a progress dialog to the user if one is not already being displayed and starts a progress tracking operation.
|
|
5
29
|
* @param info The string to display in the progress dialog as the current description of the operation.
|
|
@@ -10,43 +34,79 @@ import { status } from '@dsf/common/log';
|
|
|
10
34
|
* @param totalSteps The number of progress steps for the operation to be complete.
|
|
11
35
|
*/
|
|
12
36
|
export const progress = <T>(info: string | string[], items: T[], callback: (item: T) => boolean | void,
|
|
13
|
-
options?:
|
|
37
|
+
options?: ProgressOptions) => {
|
|
14
38
|
|
|
15
|
-
|
|
16
|
-
isCancellable: true,
|
|
17
|
-
showTimeElapsed: true,
|
|
18
|
-
totalSteps: items.length,
|
|
19
|
-
...options
|
|
20
|
-
};
|
|
39
|
+
const resolvedOptions = defaultProgressOptions(items.length, options);
|
|
21
40
|
|
|
22
|
-
const infoMessage =
|
|
23
|
-
startProgress(infoMessage,
|
|
41
|
+
const infoMessage = reportInfo(info);
|
|
42
|
+
startProgress(infoMessage, resolvedOptions.totalSteps, resolvedOptions.isCancellable, resolvedOptions.showTimeElapsed);
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
try {
|
|
45
|
+
// const total = Math.max(1, options.totalSteps ?? items.length);
|
|
46
|
+
// let lastQuarter = -1; // -1 so first boundary triggers
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < items.length; i++) {
|
|
49
|
+
// cancellation
|
|
50
|
+
if (resolvedOptions.isCancellable && progressIsCancelled()) {
|
|
51
|
+
processEvents(); // flush once on cancel
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// work
|
|
56
|
+
if (callback && callback(items[i]) === false) break;
|
|
26
57
|
|
|
27
|
-
|
|
28
|
-
|
|
58
|
+
// progress
|
|
59
|
+
stepProgress(1);
|
|
29
60
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
processEvents();
|
|
34
|
-
|
|
61
|
+
// 25/50/75/100 checkpoints
|
|
62
|
+
// const quarter = Math.floor(((i + 1) * 2) / total); // 0..4
|
|
63
|
+
// if (quarter > lastQuarter) {
|
|
64
|
+
// processEvents();
|
|
65
|
+
// lastQuarter = quarter;
|
|
66
|
+
// }
|
|
35
67
|
}
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
finishProgress();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
36
73
|
|
|
37
|
-
|
|
38
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Starts a progress operation for multi-phase work that does not map to a single item array.
|
|
76
|
+
* Uses script-level cleanup for normal returns and catchable script exceptions; host crashes or native aborts can still bypass script cleanup.
|
|
77
|
+
* @param info The string to display in the progress dialog as the current description of the operation.
|
|
78
|
+
* @param totalSteps The number of progress steps for the operation to be complete.
|
|
79
|
+
* @param callback The function to run with a manual progress handle.
|
|
80
|
+
* @param options Progress dialog options consistent with the array-driven progress helper.
|
|
81
|
+
*/
|
|
82
|
+
export const withProgress = <T>(info: string | string[], totalSteps: number, callback: (progress: ProgressHandle) => T,
|
|
83
|
+
options?: Omit<ProgressOptions, 'totalSteps'>): T => {
|
|
39
84
|
|
|
40
|
-
|
|
41
|
-
|
|
85
|
+
const resolvedOptions = defaultProgressOptions(totalSteps, options);
|
|
86
|
+
const infoMessage = reportInfo(info);
|
|
87
|
+
startProgress(infoMessage, resolvedOptions.totalSteps, resolvedOptions.isCancellable, resolvedOptions.showTimeElapsed);
|
|
42
88
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
let flushedCancellation = false;
|
|
90
|
+
const handle: ProgressHandle = {
|
|
91
|
+
isCancelled: () => {
|
|
92
|
+
const cancelled = resolvedOptions.isCancellable && Boolean(progressIsCancelled());
|
|
93
|
+
if (cancelled && !flushedCancellation) {
|
|
94
|
+
processEvents();
|
|
95
|
+
flushedCancellation = true;
|
|
96
|
+
}
|
|
97
|
+
return cancelled;
|
|
98
|
+
},
|
|
99
|
+
step: (count: number = 1) => {
|
|
100
|
+
if (handle.isCancelled()) return false;
|
|
101
|
+
stepProgress(count);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
49
104
|
}
|
|
50
105
|
|
|
51
|
-
|
|
52
|
-
|
|
106
|
+
try {
|
|
107
|
+
return callback(handle);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
finishProgress();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -5,6 +5,8 @@ import { afterEach, describe, expect, it } from 'vitest'
|
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
loadEnvFile,
|
|
8
|
+
getFixtureBuildDependencies,
|
|
9
|
+
getNpmInvocation,
|
|
8
10
|
readIntegrationResult,
|
|
9
11
|
resolveIntegrationOptions,
|
|
10
12
|
} = require('../../dist/scripts/integration')
|
|
@@ -78,6 +80,34 @@ describe('integration option resolution', () => {
|
|
|
78
80
|
})
|
|
79
81
|
})
|
|
80
82
|
|
|
83
|
+
describe('integration command resolution', () => {
|
|
84
|
+
it('uses node plus npm-cli on Windows so child_process can spawn without a shell', () => {
|
|
85
|
+
const invocation = getNpmInvocation(['--version'], 'win32')
|
|
86
|
+
|
|
87
|
+
expect(invocation.command).toMatch(/node(\.exe)?$/i)
|
|
88
|
+
expect(invocation.args[0]).toMatch(/npm-cli\.js$/)
|
|
89
|
+
expect(invocation.args[1]).toBe('--version')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('uses npm on non-Windows platforms', () => {
|
|
93
|
+
expect(getNpmInvocation(['--version'], 'linux')).toEqual({
|
|
94
|
+
command: 'npm',
|
|
95
|
+
args: ['--version']
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('integration fixture build dependencies', () => {
|
|
101
|
+
it('includes webpack loader dependencies needed by generated fixture projects', () => {
|
|
102
|
+
const dependencies = getFixtureBuildDependencies(path.resolve(__dirname, '../..'))
|
|
103
|
+
|
|
104
|
+
expect(dependencies['babel-loader']).toBeTruthy()
|
|
105
|
+
expect(dependencies['ts-loader']).toBeTruthy()
|
|
106
|
+
expect(dependencies['webpack']).toBeTruthy()
|
|
107
|
+
expect(dependencies['typescript']).toBeTruthy()
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
81
111
|
describe('integration result reader', () => {
|
|
82
112
|
it('accepts successful result JSON', () => {
|
|
83
113
|
const projectDir = makeProject()
|